more pedantic rust types for project specific settings versus user settings (#5575)

* move debug_panel to app settings

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* move showDebugPanel to app settings with backwards compatibility;

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* more pedantic project specific settings

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Jess Frazelle
2025-02-28 16:15:57 -08:00
committed by GitHub
parent 466c23a9d8
commit c18546fb2f
13 changed files with 217 additions and 136 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -10,13 +10,13 @@ export const TEST_SETTINGS = {
theme: Themes.Dark, theme: Themes.Dark,
onboardingStatus: 'dismissed', onboardingStatus: 'dismissed',
projectDirectory: '', projectDirectory: '',
enableSSAO: false, showDebugPanel: true,
}, },
modeling: { modeling: {
enableSSAO: false,
defaultUnit: 'in', defaultUnit: 'in',
mouseControls: 'Zoo', mouseControls: 'Zoo',
cameraProjection: 'perspective', cameraProjection: 'perspective',
showDebugPanel: true,
}, },
projects: { projects: {
defaultProjectName: 'project-$nnn', defaultProjectName: 'project-$nnn',
@ -59,12 +59,12 @@ export const TEST_SETTINGS_CORRUPTED = {
theme: Themes.Dark, theme: Themes.Dark,
onboardingStatus: 'dismissed', onboardingStatus: 'dismissed',
projectDirectory: 123 as any, projectDirectory: 123 as any,
showDebugPanel: true,
}, },
modeling: { modeling: {
defaultUnit: 'invalid' as any, defaultUnit: 'invalid' as any,
mouseControls: `() => alert('hack the planet')` as any, mouseControls: `() => alert('hack the planet')` as any,
cameraProjection: 'perspective', cameraProjection: 'perspective',
showDebugPanel: true,
}, },
projects: { projects: {
defaultProjectName: false as any, defaultProjectName: false as any,

View File

@ -62,7 +62,7 @@ test.describe('Testing settings', () => {
name: 'Settings', name: 'Settings',
exact: true, exact: true,
}) })
const inputLocator = page.locator('input[name="modeling-showDebugPanel"]') const inputLocator = page.locator('input[name="app-showDebugPanel"]')
await test.step('Open settings dialog and set "Show debug panel" to on', async () => { await test.step('Open settings dialog and set "Show debug panel" to on', async () => {
await page.keyboard.press('ControlOrMeta+,') await page.keyboard.press('ControlOrMeta+,')
@ -125,7 +125,7 @@ test.describe('Testing settings', () => {
// Check that the project setting did not change // Check that the project setting did not change
await page.getByRole('radio', { name: 'Project' }).click() await page.getByRole('radio', { name: 'Project' }).click()
await expect( await expect(
page.locator('input[name="modeling-showDebugPanel"]') page.locator('input[name="app-showDebugPanel"]')
).not.toBeChecked() ).not.toBeChecked()
} }
) )
@ -833,7 +833,8 @@ test.describe('Testing settings', () => {
// but "show debug panel" set to false // but "show debug panel" set to false
appSettings: { appSettings: {
...TEST_SETTINGS, ...TEST_SETTINGS,
modeling: { ...TEST_SETTINGS.modeling, showDebugPanel: false }, app: { ...TEST_SETTINGS.app, showDebugPanel: false },
modeling: { ...TEST_SETTINGS.modeling },
}, },
}, },
async ({ context, page, homePage }) => { async ({ context, page, homePage }) => {
@ -853,7 +854,7 @@ test.describe('Testing settings', () => {
const debugPaneButton = page.getByTestId('debug-pane-button') const debugPaneButton = page.getByTestId('debug-pane-button')
const commandsButton = page.getByRole('button', { name: 'Commands' }) const commandsButton = page.getByRole('button', { name: 'Commands' })
const debugPaneOption = page.getByRole('option', { const debugPaneOption = page.getByRole('option', {
name: 'Settings · modeling · show debug panel', name: 'Settings · app · show debug panel',
}) })
async function setShowDebugPanelTo(value: 'On' | 'Off') { async function setShowDebugPanelTo(value: 'On' | 'Off') {

View File

@ -132,13 +132,14 @@ export const ModelingMachineProvider = ({
children: React.ReactNode children: React.ReactNode
}) => { }) => {
const { const {
app: { theme, enableSSAO, allowOrbitInSketchMode }, app: { theme, allowOrbitInSketchMode },
modeling: { modeling: {
defaultUnit, defaultUnit,
cameraProjection, cameraProjection,
highlightEdges, highlightEdges,
showScaleGrid, showScaleGrid,
cameraOrbit, cameraOrbit,
enableSSAO,
}, },
} = useSettings() } = useSettings()
const previousAllowOrbitInSketchMode = useRef(allowOrbitInSketchMode.current) const previousAllowOrbitInSketchMode = useRef(allowOrbitInSketchMode.current)

View File

@ -234,6 +234,6 @@ export const sidebarPanes: SidebarPane[] = [
) )
}, },
keybinding: 'Shift + D', keybinding: 'Shift + D',
hide: ({ settings }) => !settings.modeling.showDebugPanel.current, hide: ({ settings }) => !settings.app.showDebugPanel.current,
}, },
] ]

View File

@ -48,7 +48,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
context.store?.openPanes.length === 0 context.store?.openPanes.length === 0
? 'pointer-events-none ' ? 'pointer-events-none '
: 'pointer-events-auto ' : 'pointer-events-auto '
const showDebugPanel = settings.modeling.showDebugPanel const showDebugPanel = settings.app.showDebugPanel
const paneCallbackProps = useMemo( const paneCallbackProps = useMemo(
() => ({ () => ({
@ -149,7 +149,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
}, },
}) })
} }
}, [settings.modeling.showDebugPanel]) }, [settings.app.showDebugPanel])
const togglePane = useCallback( const togglePane = useCallback(
(newPane: SidebarType) => { (newPane: SidebarType) => {

View File

@ -174,12 +174,17 @@ export function createSettings() {
</div> </div>
), ),
}), }),
enableSSAO: new Setting<boolean>({ /**
defaultValue: true, * Whether to show the debug panel, which lets you see
description: * various states of the app to aid in development
'Whether or not Screen Space Ambient Occlusion (SSAO) is enabled', */
showDebugPanel: new Setting<boolean>({
defaultValue: false,
description: 'Whether to show the debug panel, a development tool',
validate: (v) => typeof v === 'boolean', validate: (v) => typeof v === 'boolean',
hideOnPlatform: 'both', //for now commandConfig: {
inputType: 'boolean',
},
}), }),
/** /**
* Stream resource saving behavior toggle * Stream resource saving behavior toggle
@ -297,6 +302,13 @@ export function createSettings() {
})), })),
}, },
}), }),
enableSSAO: new Setting<boolean>({
defaultValue: true,
description:
'Whether or not Screen Space Ambient Occlusion (SSAO) is enabled',
validate: (v) => typeof v === 'boolean',
hideOnPlatform: 'both', //for now
}),
/** /**
* The controls for how to navigate the 3D view * The controls for how to navigate the 3D view
*/ */
@ -435,18 +447,6 @@ export function createSettings() {
}, },
hideOnLevel: 'project', hideOnLevel: 'project',
}), }),
/**
* Whether to show the debug panel, which lets you see
* various states of the app to aid in development
*/
showDebugPanel: new Setting<boolean>({
defaultValue: false,
description: 'Whether to show the debug panel, a development tool',
validate: (v) => typeof v === 'boolean',
commandConfig: {
inputType: 'boolean',
},
}),
/** /**
* TODO: This setting is not yet implemented. * TODO: This setting is not yet implemented.
* Whether to turn off animations and other motion effects * Whether to turn off animations and other motion effects

View File

@ -46,7 +46,7 @@ export function configurationToSettingsPayload(
allowOrbitInSketchMode: allowOrbitInSketchMode:
configuration?.settings?.app?.allow_orbit_in_sketch_mode, configuration?.settings?.app?.allow_orbit_in_sketch_mode,
projectDirectory: configuration?.settings?.project?.directory, projectDirectory: configuration?.settings?.project?.directory,
enableSSAO: configuration?.settings?.modeling?.enable_ssao, showDebugPanel: configuration?.settings?.app?.show_debug_panel,
}, },
modeling: { modeling: {
defaultUnit: configuration?.settings?.modeling?.base_unit, defaultUnit: configuration?.settings?.modeling?.base_unit,
@ -56,7 +56,7 @@ export function configurationToSettingsPayload(
configuration?.settings?.modeling?.mouse_controls configuration?.settings?.modeling?.mouse_controls
), ),
highlightEdges: configuration?.settings?.modeling?.highlight_edges, highlightEdges: configuration?.settings?.modeling?.highlight_edges,
showDebugPanel: configuration?.settings?.modeling?.show_debug_panel, enableSSAO: configuration?.settings?.modeling?.enable_ssao,
showScaleGrid: configuration?.settings?.modeling?.show_scale_grid, showScaleGrid: configuration?.settings?.modeling?.show_scale_grid,
}, },
textEditor: { textEditor: {
@ -124,18 +124,15 @@ export function projectConfigurationToSettingsPayload(
streamIdleMode: configuration?.settings?.app?.stream_idle_mode, streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
allowOrbitInSketchMode: allowOrbitInSketchMode:
configuration?.settings?.app?.allow_orbit_in_sketch_mode, configuration?.settings?.app?.allow_orbit_in_sketch_mode,
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
namedViews: deepPartialNamedViewsToNamedViews( namedViews: deepPartialNamedViewsToNamedViews(
configuration?.settings?.app?.named_views configuration?.settings?.app?.named_views
), ),
showDebugPanel: configuration?.settings?.app?.show_debug_panel,
}, },
modeling: { modeling: {
defaultUnit: configuration?.settings?.modeling?.base_unit, defaultUnit: configuration?.settings?.modeling?.base_unit,
mouseControls: mouseControlsToCameraSystem(
configuration?.settings?.modeling?.mouse_controls
),
highlightEdges: configuration?.settings?.modeling?.highlight_edges, highlightEdges: configuration?.settings?.modeling?.highlight_edges,
showDebugPanel: configuration?.settings?.modeling?.show_debug_panel, enableSSAO: configuration?.settings?.modeling?.enable_ssao,
}, },
textEditor: { textEditor: {
textWrapping: configuration?.settings?.text_editor?.text_wrapping, textWrapping: configuration?.settings?.text_editor?.text_wrapping,

View File

@ -279,7 +279,7 @@ impl From<crate::settings::types::project::ProjectConfiguration> for ExecutorSet
units: config.settings.modeling.base_unit, units: config.settings.modeling.base_unit,
highlight_edges: config.settings.modeling.highlight_edges.into(), highlight_edges: config.settings.modeling.highlight_edges.into(),
enable_ssao: config.settings.modeling.enable_ssao.into(), enable_ssao: config.settings.modeling.enable_ssao.into(),
show_grid: config.settings.modeling.show_scale_grid, show_grid: Default::default(),
replay: None, replay: None,
project_directory: None, project_directory: None,
current_file: None, current_file: None,

View File

@ -3,7 +3,6 @@
pub mod project; pub mod project;
use anyhow::Result; use anyhow::Result;
use indexmap::IndexMap;
use parse_display::{Display, FromStr}; use parse_display::{Display, FromStr};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -56,6 +55,11 @@ impl Configuration {
} }
} }
if settings.settings.modeling.show_debug_panel && !settings.settings.app.show_debug_panel {
settings.settings.app.show_debug_panel = settings.settings.modeling.show_debug_panel;
settings.settings.modeling.show_debug_panel = Default::default();
}
settings.validate()?; settings.validate()?;
Ok(settings) Ok(settings)
@ -121,13 +125,14 @@ pub struct AppSettings {
pub dismiss_web_banner: bool, pub dismiss_web_banner: bool,
/// When the user is idle, and this is true, the stream will be torn down. /// When the user is idle, and this is true, the stream will be torn down.
#[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")] #[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")]
stream_idle_mode: bool, pub stream_idle_mode: bool,
/// When the user is idle, and this is true, the stream will be torn down. /// When the user is idle, and this is true, the stream will be torn down.
#[serde(default, alias = "allowOrbitInSketchMode", skip_serializing_if = "is_default")] #[serde(default, alias = "allowOrbitInSketchMode", skip_serializing_if = "is_default")]
allow_orbit_in_sketch_mode: bool, pub allow_orbit_in_sketch_mode: bool,
/// Settings that affect the behavior of the command bar. /// Whether to show the debug panel, which lets you see various states
#[serde(default, alias = "namedViews", skip_serializing_if = "IndexMap::is_empty")] /// of the app to aid in development.
pub named_views: IndexMap<uuid::Uuid, NamedView>, #[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
pub show_debug_panel: bool,
} }
// TODO: When we remove backwards compatibility with the old settings file, we can remove this. // TODO: When we remove backwards compatibility with the old settings file, we can remove this.
@ -274,6 +279,7 @@ pub struct ModelingSettings {
pub highlight_edges: DefaultTrue, pub highlight_edges: DefaultTrue,
/// Whether to show the debug panel, which lets you see various states /// Whether to show the debug panel, which lets you see various states
/// of the app to aid in development. /// of the app to aid in development.
/// Remove this when we remove backwards compatibility with the old settings file.
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")] #[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
pub show_debug_panel: bool, pub show_debug_panel: bool,
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled. /// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
@ -284,46 +290,6 @@ pub struct ModelingSettings {
pub show_scale_grid: bool, pub show_scale_grid: bool,
} }
fn named_view_point_version_one() -> f64 {
1.0
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, Validate, PartialEq)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub struct NamedView {
/// User defined name to identify the named view. A label.
#[serde(default, alias = "name", skip_serializing_if = "is_default")]
pub name: String,
/// Engine camera eye off set
#[serde(default, alias = "eyeOffset", skip_serializing_if = "is_default")]
pub eye_offset: f64,
/// Engine camera vertical FOV
#[serde(default, alias = "fovY", skip_serializing_if = "is_default")]
pub fov_y: f64,
// Engine camera is orthographic or perspective projection
#[serde(default, alias = "isOrtho")]
pub is_ortho: bool,
/// Engine camera is orthographic camera scaling enabled
#[serde(default, alias = "orthoScaleEnabled")]
pub ortho_scale_enabled: bool,
/// Engine camera orthographic scaling factor
#[serde(default, alias = "orthoScaleFactor", skip_serializing_if = "is_default")]
pub ortho_scale_factor: f64,
/// Engine camera position that the camera pivots around
#[serde(default, alias = "pivotPosition", skip_serializing_if = "is_default")]
pub pivot_position: [f64; 3],
/// Engine camera orientation in relation to the pivot position
#[serde(default, alias = "pivotRotation", skip_serializing_if = "is_default")]
pub pivot_rotation: [f64; 4],
/// Engine camera world coordinate system orientation
#[serde(default, alias = "worldCoordSystem", skip_serializing_if = "is_default")]
pub world_coord_system: String,
/// Version number of the view point if the engine camera API changes
#[serde(default = "named_view_point_version_one")]
pub version: f64,
}
#[derive(Debug, Copy, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Eq)] #[derive(Debug, Copy, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Eq)]
#[ts(export)] #[ts(export)]
#[serde(transparent)] #[serde(transparent)]
@ -610,7 +576,6 @@ mod tests {
ModelingSettings, OnboardingStatus, ProjectSettings, Settings, TextEditorSettings, UnitLength, ModelingSettings, OnboardingStatus, ProjectSettings, Settings, TextEditorSettings, UnitLength,
}; };
use crate::settings::types::CameraOrbitType; use crate::settings::types::CameraOrbitType;
use indexmap::IndexMap;
#[test] #[test]
// Test that we can deserialize a project file from the old format. // Test that we can deserialize a project file from the old format.
@ -654,15 +619,15 @@ textWrapping = true
enable_ssao: None, enable_ssao: None,
stream_idle_mode: false, stream_idle_mode: false,
allow_orbit_in_sketch_mode: false, allow_orbit_in_sketch_mode: false,
named_views: IndexMap::default() show_debug_panel: true,
}, },
modeling: ModelingSettings { modeling: ModelingSettings {
base_unit: UnitLength::In, base_unit: UnitLength::In,
camera_projection: CameraProjectionType::Orthographic, camera_projection: CameraProjectionType::Orthographic,
camera_orbit: Default::default(), camera_orbit: Default::default(),
mouse_controls: Default::default(), mouse_controls: Default::default(),
show_debug_panel: Default::default(),
highlight_edges: Default::default(), highlight_edges: Default::default(),
show_debug_panel: true,
enable_ssao: false.into(), enable_ssao: false.into(),
show_scale_grid: false, show_scale_grid: false,
}, },
@ -716,9 +681,9 @@ includeSettings = false
theme_color: None, theme_color: None,
dismiss_web_banner: false, dismiss_web_banner: false,
enable_ssao: None, enable_ssao: None,
show_debug_panel: true,
stream_idle_mode: false, stream_idle_mode: false,
allow_orbit_in_sketch_mode: false, allow_orbit_in_sketch_mode: false,
named_views: IndexMap::default()
}, },
modeling: ModelingSettings { modeling: ModelingSettings {
base_unit: UnitLength::Yd, base_unit: UnitLength::Yd,
@ -726,9 +691,9 @@ includeSettings = false
camera_orbit: Default::default(), camera_orbit: Default::default(),
mouse_controls: Default::default(), mouse_controls: Default::default(),
highlight_edges: Default::default(), highlight_edges: Default::default(),
show_debug_panel: true,
enable_ssao: true.into(), enable_ssao: true.into(),
show_scale_grid: false, show_scale_grid: false,
show_debug_panel: Default::default(),
}, },
text_editor: TextEditorSettings { text_editor: TextEditorSettings {
text_wrapping: false.into(), text_wrapping: false.into(),
@ -787,7 +752,7 @@ defaultProjectName = "projects-$nnn"
enable_ssao: None, enable_ssao: None,
stream_idle_mode: false, stream_idle_mode: false,
allow_orbit_in_sketch_mode: false, allow_orbit_in_sketch_mode: false,
named_views: IndexMap::default() show_debug_panel: true,
}, },
modeling: ModelingSettings { modeling: ModelingSettings {
base_unit: UnitLength::Yd, base_unit: UnitLength::Yd,
@ -795,7 +760,7 @@ defaultProjectName = "projects-$nnn"
camera_orbit: CameraOrbitType::Spherical, camera_orbit: CameraOrbitType::Spherical,
mouse_controls: Default::default(), mouse_controls: Default::default(),
highlight_edges: Default::default(), highlight_edges: Default::default(),
show_debug_panel: true, show_debug_panel: Default::default(),
enable_ssao: true.into(), enable_ssao: true.into(),
show_scale_grid: false, show_scale_grid: false,
}, },
@ -820,6 +785,7 @@ defaultProjectName = "projects-$nnn"
serialized, serialized,
r#"[settings.app] r#"[settings.app]
onboarding_status = "dismissed" onboarding_status = "dismissed"
show_debug_panel = true
[settings.app.appearance] [settings.app.appearance]
theme = "dark" theme = "dark"
@ -827,7 +793,6 @@ color = 138.0
[settings.modeling] [settings.modeling]
base_unit = "yd" base_unit = "yd"
show_debug_panel = true
[settings.text_editor] [settings.text_editor]
text_wrapping = false text_wrapping = false
@ -866,9 +831,9 @@ projectDirectory = "/Users/macinatormax/Documents/kittycad-modeling-projects""#;
theme_color: None, theme_color: None,
dismiss_web_banner: false, dismiss_web_banner: false,
enable_ssao: None, enable_ssao: None,
show_debug_panel: false,
stream_idle_mode: false, stream_idle_mode: false,
allow_orbit_in_sketch_mode: false, allow_orbit_in_sketch_mode: false,
named_views: IndexMap::default()
}, },
modeling: ModelingSettings { modeling: ModelingSettings {
base_unit: UnitLength::Mm, base_unit: UnitLength::Mm,
@ -876,7 +841,7 @@ projectDirectory = "/Users/macinatormax/Documents/kittycad-modeling-projects""#;
camera_orbit: Default::default(), camera_orbit: Default::default(),
mouse_controls: Default::default(), mouse_controls: Default::default(),
highlight_edges: true.into(), highlight_edges: true.into(),
show_debug_panel: false, show_debug_panel: Default::default(),
enable_ssao: true.into(), enable_ssao: true.into(),
show_scale_grid: false, show_scale_grid: false,
}, },

View File

@ -1,12 +1,13 @@
//! Types specific for modeling-app projects. //! Types specific for modeling-app projects.
use anyhow::Result; use anyhow::Result;
use indexmap::IndexMap;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
use crate::settings::types::{ use crate::settings::types::{
AppColor, AppSettings, AppTheme, CommandBarSettings, ModelingSettings, TextEditorSettings, is_default, AppColor, CommandBarSettings, DefaultTrue, FloatOrInt, OnboardingStatus, TextEditorSettings, UnitLength,
}; };
/// High level project configuration. /// High level project configuration.
@ -24,14 +25,6 @@ impl ProjectConfiguration {
// TODO: remove this when we remove backwards compatibility with the old settings file. // TODO: remove this when we remove backwards compatibility with the old settings file.
pub fn backwards_compatible_toml_parse(toml_str: &str) -> Result<Self> { pub fn backwards_compatible_toml_parse(toml_str: &str) -> Result<Self> {
let mut settings = toml::from_str::<Self>(toml_str)?; let mut settings = toml::from_str::<Self>(toml_str)?;
settings.settings.app.project_directory = None;
if let Some(theme) = &settings.settings.app.theme {
if settings.settings.app.appearance.theme == AppTheme::default() {
settings.settings.app.appearance.theme = *theme;
settings.settings.app.theme = None;
}
}
if let Some(theme_color) = &settings.settings.app.theme_color { if let Some(theme_color) = &settings.settings.app.theme_color {
if settings.settings.app.appearance.color == AppColor::default() { if settings.settings.app.appearance.color == AppColor::default() {
@ -47,6 +40,11 @@ impl ProjectConfiguration {
} }
} }
if settings.settings.modeling.show_debug_panel && !settings.settings.app.show_debug_panel {
settings.settings.app.show_debug_panel = settings.settings.modeling.show_debug_panel;
settings.settings.modeling.show_debug_panel = Default::default();
}
settings.validate()?; settings.validate()?;
Ok(settings) Ok(settings)
@ -61,11 +59,11 @@ pub struct PerProjectSettings {
/// The settings for the modeling app. /// The settings for the modeling app.
#[serde(default)] #[serde(default)]
#[validate(nested)] #[validate(nested)]
pub app: AppSettings, pub app: ProjectAppSettings,
/// Settings that affect the behavior while modeling. /// Settings that affect the behavior while modeling.
#[serde(default)] #[serde(default)]
#[validate(nested)] #[validate(nested)]
pub modeling: ModelingSettings, pub modeling: ProjectModelingSettings,
/// Settings that affect the behavior of the KCL text editor. /// Settings that affect the behavior of the KCL text editor.
#[serde(default, alias = "textEditor")] #[serde(default, alias = "textEditor")]
#[validate(nested)] #[validate(nested)]
@ -76,15 +74,126 @@ pub struct PerProjectSettings {
pub command_bar: CommandBarSettings, pub command_bar: CommandBarSettings,
} }
/// Project specific application settings.
// TODO: When we remove backwards compatibility with the old settings file, we can remove the
// aliases to camelCase (and projects plural) from everywhere.
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Validate)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
pub struct ProjectAppSettings {
/// The settings for the appearance of the app.
#[serde(default, skip_serializing_if = "is_default")]
#[validate(nested)]
pub appearance: ProjectAppearanceSettings,
/// The onboarding status of the app.
#[serde(default, alias = "onboardingStatus", skip_serializing_if = "is_default")]
pub onboarding_status: OnboardingStatus,
/// The hue of the primary theme color for the app.
#[serde(default, skip_serializing_if = "Option::is_none", alias = "themeColor")]
pub theme_color: Option<FloatOrInt>,
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
#[serde(default, alias = "enableSSAO", skip_serializing_if = "Option::is_none")]
pub enable_ssao: Option<bool>,
/// Permanently dismiss the banner warning to download the desktop app.
/// This setting only applies to the web app. And is temporary until we have Linux support.
#[serde(default, alias = "dismissWebBanner", skip_serializing_if = "is_default")]
pub dismiss_web_banner: bool,
/// When the user is idle, and this is true, the stream will be torn down.
#[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")]
pub stream_idle_mode: bool,
/// When the user is idle, and this is true, the stream will be torn down.
#[serde(default, alias = "allowOrbitInSketchMode", skip_serializing_if = "is_default")]
pub allow_orbit_in_sketch_mode: bool,
/// Whether to show the debug panel, which lets you see various states
/// of the app to aid in development.
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
pub show_debug_panel: bool,
/// Settings that affect the behavior of the command bar.
#[serde(default, alias = "namedViews", skip_serializing_if = "IndexMap::is_empty")]
pub named_views: IndexMap<uuid::Uuid, NamedView>,
}
/// Project specific appearance settings.
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Validate)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
pub struct ProjectAppearanceSettings {
/// The hue of the primary theme color for the app.
#[serde(default, skip_serializing_if = "is_default")]
#[validate(nested)]
pub color: AppColor,
}
/// Project specific settings that affect the behavior while modeling.
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Eq, Validate)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub struct ProjectModelingSettings {
/// The default unit to use in modeling dimensions.
#[serde(default, alias = "defaultUnit", skip_serializing_if = "is_default")]
pub base_unit: UnitLength,
/// Highlight edges of 3D objects?
#[serde(default, alias = "highlightEdges", skip_serializing_if = "is_default")]
pub highlight_edges: DefaultTrue,
/// Whether to show the debug panel, which lets you see various states
/// of the app to aid in development.
/// Remove this when we remove backwards compatibility with the old settings file.
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
pub show_debug_panel: bool,
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
#[serde(default, skip_serializing_if = "is_default")]
pub enable_ssao: DefaultTrue,
}
fn named_view_point_version_one() -> f64 {
1.0
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub struct NamedView {
/// User defined name to identify the named view. A label.
#[serde(default, alias = "name", skip_serializing_if = "is_default")]
pub name: String,
/// Engine camera eye off set
#[serde(default, alias = "eyeOffset", skip_serializing_if = "is_default")]
pub eye_offset: f64,
/// Engine camera vertical FOV
#[serde(default, alias = "fovY", skip_serializing_if = "is_default")]
pub fov_y: f64,
// Engine camera is orthographic or perspective projection
#[serde(default, alias = "isOrtho")]
pub is_ortho: bool,
/// Engine camera is orthographic camera scaling enabled
#[serde(default, alias = "orthoScaleEnabled")]
pub ortho_scale_enabled: bool,
/// Engine camera orthographic scaling factor
#[serde(default, alias = "orthoScaleFactor", skip_serializing_if = "is_default")]
pub ortho_scale_factor: f64,
/// Engine camera position that the camera pivots around
#[serde(default, alias = "pivotPosition", skip_serializing_if = "is_default")]
pub pivot_position: [f64; 3],
/// Engine camera orientation in relation to the pivot position
#[serde(default, alias = "pivotRotation", skip_serializing_if = "is_default")]
pub pivot_rotation: [f64; 4],
/// Engine camera world coordinate system orientation
#[serde(default, alias = "worldCoordSystem", skip_serializing_if = "is_default")]
pub world_coord_system: String,
/// Version number of the view point if the engine camera API changes
#[serde(default = "named_view_point_version_one")]
pub version: f64,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use super::{ use super::{
AppSettings, AppTheme, CommandBarSettings, ModelingSettings, PerProjectSettings, ProjectConfiguration, CommandBarSettings, NamedView, PerProjectSettings, ProjectAppSettings, ProjectAppearanceSettings,
TextEditorSettings, ProjectConfiguration, ProjectModelingSettings, TextEditorSettings,
}; };
use crate::settings::types::{AppearanceSettings, NamedView, UnitLength}; use crate::settings::types::UnitLength;
use indexmap::IndexMap; use indexmap::IndexMap;
use serde_json::Value; use serde_json::Value;
@ -94,13 +203,15 @@ mod tests {
// TODO: We can remove this functionality after a few versions. // TODO: We can remove this functionality after a few versions.
fn test_backwards_compatible_project_settings_file() { fn test_backwards_compatible_project_settings_file() {
let old_project_file = r#"[settings.app] let old_project_file = r#"[settings.app]
theme = "dark"
themeColor = "138" themeColor = "138"
[settings.textEditor] [settings.textEditor]
textWrapping = false textWrapping = false
blinkingCursor = false blinkingCursor = false
[settings.modeling]
showDebugPanel = true
[settings.commandBar] [settings.commandBar]
includeSettings = false includeSettings = false
#"#; #"#;
@ -111,30 +222,22 @@ includeSettings = false
parsed, parsed,
ProjectConfiguration { ProjectConfiguration {
settings: PerProjectSettings { settings: PerProjectSettings {
app: AppSettings { app: ProjectAppSettings {
appearance: AppearanceSettings { appearance: ProjectAppearanceSettings { color: 138.0.into() },
theme: AppTheme::Dark,
color: 138.0.into()
},
onboarding_status: Default::default(), onboarding_status: Default::default(),
project_directory: None,
theme: None,
theme_color: None, theme_color: None,
dismiss_web_banner: false, dismiss_web_banner: false,
enable_ssao: None, enable_ssao: None,
stream_idle_mode: false, stream_idle_mode: false,
allow_orbit_in_sketch_mode: false, allow_orbit_in_sketch_mode: false,
show_debug_panel: true,
named_views: IndexMap::default() named_views: IndexMap::default()
}, },
modeling: ModelingSettings { modeling: ProjectModelingSettings {
base_unit: UnitLength::Mm, base_unit: UnitLength::Mm,
camera_projection: Default::default(),
camera_orbit: Default::default(),
mouse_controls: Default::default(),
highlight_edges: Default::default(), highlight_edges: Default::default(),
show_debug_panel: false, show_debug_panel: Default::default(),
enable_ssao: true.into(), enable_ssao: true.into(),
show_scale_grid: false,
}, },
text_editor: TextEditorSettings { text_editor: TextEditorSettings {
text_wrapping: false.into(), text_wrapping: false.into(),
@ -146,6 +249,27 @@ includeSettings = false
} }
} }
); );
// Write the file back out.
let serialized = toml::to_string(&parsed).unwrap();
assert_eq!(
serialized,
r#"[settings.app]
show_debug_panel = true
[settings.app.appearance]
color = 138.0
[settings.modeling]
[settings.text_editor]
text_wrapping = false
blinking_cursor = false
[settings.command_bar]
include_settings = false
"#
);
} }
#[test] #[test]
@ -243,19 +367,15 @@ color = 1567.4"#;
fn test_project_settings_named_views() { fn test_project_settings_named_views() {
let conf = ProjectConfiguration { let conf = ProjectConfiguration {
settings: PerProjectSettings { settings: PerProjectSettings {
app: AppSettings { app: ProjectAppSettings {
appearance: AppearanceSettings { appearance: ProjectAppearanceSettings { color: 138.0.into() },
theme: AppTheme::Dark,
color: 138.0.into(),
},
onboarding_status: Default::default(), onboarding_status: Default::default(),
project_directory: None,
theme: None,
theme_color: None, theme_color: None,
dismiss_web_banner: false, dismiss_web_banner: false,
enable_ssao: None, enable_ssao: None,
stream_idle_mode: false, stream_idle_mode: false,
allow_orbit_in_sketch_mode: false, allow_orbit_in_sketch_mode: false,
show_debug_panel: true,
named_views: IndexMap::from([ named_views: IndexMap::from([
( (
uuid::uuid!("323611ea-66e3-43c9-9d0d-1091ba92948c"), uuid::uuid!("323611ea-66e3-43c9-9d0d-1091ba92948c"),
@ -289,15 +409,11 @@ color = 1567.4"#;
), ),
]), ]),
}, },
modeling: ModelingSettings { modeling: ProjectModelingSettings {
base_unit: UnitLength::Yd, base_unit: UnitLength::Yd,
camera_projection: Default::default(),
camera_orbit: Default::default(),
mouse_controls: Default::default(),
highlight_edges: Default::default(), highlight_edges: Default::default(),
show_debug_panel: true, show_debug_panel: Default::default(),
enable_ssao: true.into(), enable_ssao: true.into(),
show_scale_grid: false,
}, },
text_editor: TextEditorSettings { text_editor: TextEditorSettings {
text_wrapping: false.into(), text_wrapping: false.into(),
@ -309,8 +425,10 @@ color = 1567.4"#;
}, },
}; };
let serialized = toml::to_string(&conf).unwrap(); let serialized = toml::to_string(&conf).unwrap();
let old_project_file = r#"[settings.app.appearance] let old_project_file = r#"[settings.app]
theme = "dark" show_debug_panel = true
[settings.app.appearance]
color = 138.0 color = 138.0
[settings.app.named_views.323611ea-66e3-43c9-9d0d-1091ba92948c] [settings.app.named_views.323611ea-66e3-43c9-9d0d-1091ba92948c]
@ -339,7 +457,6 @@ version = 1.0
[settings.modeling] [settings.modeling]
base_unit = "yd" base_unit = "yd"
show_debug_panel = true
[settings.text_editor] [settings.text_editor]
text_wrapping = false text_wrapping = false