Coordinate grid uses same scale as user's units (#7465)

Closes https://github.com/KittyCAD/engine/issues/3494. Thanks to @nadr0 for helping on the JS side.

If users set their units, the grid will stop auto scaling, and instead will be set to 10 of whatever unit they used. 

If users set their units, and those units are metric, then it'll include a scale bar (see screenshot). Imperial units won't have that bar. 

This behaviour is configurable via settings.

## Limitations

 - The scale bar below the grid cannot be disabled in metric units, and cannot be enabled in imperial units

<img width="1690" alt="Screenshot 2025-06-05 at 7 51 41 PM" src="https://github.com/user-attachments/assets/c597087c-f96d-4c30-95f4-b3d8ba2b5567" />
This commit is contained in:
Adam Chalmers
2025-06-23 17:30:26 -05:00
committed by GitHub
parent dbc87292e4
commit 478bf34f2b
147 changed files with 164 additions and 19 deletions

View File

@ -85,6 +85,13 @@ Whether to show the debug panel, which lets you see various states of the app to
**Default:** None
##### fixed_size_grid
If true, the grid cells will be fixed-size, where the width is the user&#x27;s default length unit. If false, the grid&#x27;s size will scale as the user zooms in and out.
**Default:** true
#### modeling

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -12,6 +12,7 @@ export const TEST_SETTINGS: DeepPartial<Settings> = {
},
onboarding_status: 'dismissed',
show_debug_panel: true,
fixed_size_grid: false,
},
modeling: {
enable_ssao: false,

View File

@ -880,6 +880,10 @@ export async function setup(
},
...TEST_SETTINGS.project,
onboarding_status: 'dismissed',
// Tests were written before this setting existed.
// It's true by default because it's a good user experience, but
// these tests require it to be false.
fixed_size_grid: false,
},
project: {
...TEST_SETTINGS.project,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -323,13 +323,15 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
settings: &crate::ExecutorSettings,
source_range: SourceRange,
id_generator: &mut IdGenerator,
grid_scale_unit: GridScaleBehavior,
) -> Result<(), crate::errors::KclError> {
// Set the edge visibility.
self.set_edge_visibility(settings.highlight_edges, source_range, id_generator)
.await?;
// Send the command to show the grid.
self.modify_grid(!settings.show_grid, source_range, id_generator)
self.modify_grid(!settings.show_grid, grid_scale_unit, source_range, id_generator)
.await?;
// We do not have commands for changing ssao on the fly.
@ -760,6 +762,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn modify_grid(
&self,
hidden: bool,
grid_scale_behavior: GridScaleBehavior,
source_range: SourceRange,
id_generator: &mut IdGenerator,
) -> Result<(), KclError> {
@ -774,6 +777,13 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
)
.await?;
self.batch_modeling_cmd(
id_generator.next_uuid(),
source_range,
&grid_scale_behavior.into_modeling_cmd(),
)
.await?;
// Hide/show the grid scale text.
self.batch_modeling_cmd(
id_generator.next_uuid(),
@ -886,3 +896,22 @@ pub fn new_zoo_client(token: Option<String>, engine_addr: Option<String>) -> any
Ok(client)
}
#[derive(Copy, Clone, Debug)]
pub enum GridScaleBehavior {
ScaleWithZoom,
Fixed(Option<kcmc::units::UnitLength>),
}
impl GridScaleBehavior {
fn into_modeling_cmd(self) -> ModelingCmd {
const NUMBER_OF_GRID_COLUMNS: f32 = 10.0;
match self {
GridScaleBehavior::ScaleWithZoom => ModelingCmd::from(mcmd::SetGridAutoScale {}),
GridScaleBehavior::Fixed(unit_length) => ModelingCmd::from(mcmd::SetGridScale {
value: NUMBER_OF_GRID_COLUMNS,
units: unit_length.unwrap_or(kcmc::units::UnitLength::Millimeters),
}),
}
}
}

View File

@ -31,7 +31,7 @@ pub use state::{ExecState, MetaSettings};
use uuid::Uuid;
use crate::{
engine::EngineManager,
engine::{EngineManager, GridScaleBehavior},
errors::{KclError, KclErrorDetails},
execution::{
cache::{CacheInformation, CacheResult},
@ -295,6 +295,8 @@ pub struct ExecutorSettings {
/// This is the path to the current file being executed.
/// We use this for preventing cyclic imports.
pub current_file: Option<TypedPath>,
/// Whether or not to automatically scale the grid when user zooms.
pub fixed_size_grid: bool,
}
impl Default for ExecutorSettings {
@ -306,33 +308,34 @@ impl Default for ExecutorSettings {
replay: None,
project_directory: None,
current_file: None,
fixed_size_grid: true,
}
}
}
impl From<crate::settings::types::Configuration> for ExecutorSettings {
fn from(config: crate::settings::types::Configuration) -> Self {
Self::from(config.settings)
}
}
impl From<crate::settings::types::Settings> for ExecutorSettings {
fn from(settings: crate::settings::types::Settings) -> Self {
Self {
highlight_edges: config.settings.modeling.highlight_edges.into(),
enable_ssao: config.settings.modeling.enable_ssao.into(),
show_grid: config.settings.modeling.show_scale_grid,
highlight_edges: settings.modeling.highlight_edges.into(),
enable_ssao: settings.modeling.enable_ssao.into(),
show_grid: settings.modeling.show_scale_grid,
replay: None,
project_directory: None,
current_file: None,
fixed_size_grid: settings.app.fixed_size_grid,
}
}
}
impl From<crate::settings::types::project::ProjectConfiguration> for ExecutorSettings {
fn from(config: crate::settings::types::project::ProjectConfiguration) -> Self {
Self {
highlight_edges: config.settings.modeling.highlight_edges.into(),
enable_ssao: config.settings.modeling.enable_ssao.into(),
show_grid: Default::default(),
replay: None,
project_directory: None,
current_file: None,
}
Self::from(config.settings.modeling)
}
}
@ -345,6 +348,7 @@ impl From<crate::settings::types::ModelingSettings> for ExecutorSettings {
replay: None,
project_directory: None,
current_file: None,
fixed_size_grid: true,
}
}
}
@ -358,6 +362,7 @@ impl From<crate::settings::types::project::ProjectModelingSettings> for Executor
replay: None,
project_directory: None,
current_file: None,
fixed_size_grid: true,
}
}
}
@ -497,6 +502,7 @@ impl ExecutorContext {
replay: None,
project_directory: None,
current_file: None,
fixed_size_grid: false,
},
None,
engine_addr,
@ -592,6 +598,18 @@ impl ExecutorContext {
pub async fn run_with_caching(&self, program: crate::Program) -> Result<ExecOutcome, KclErrorWithOutputs> {
assert!(!self.is_mock());
let grid_scale = if self.settings.fixed_size_grid {
GridScaleBehavior::Fixed(
program
.meta_settings()
.ok()
.flatten()
.map(|s| s.default_length_units)
.map(kcmc::units::UnitLength::from),
)
} else {
GridScaleBehavior::ScaleWithZoom
};
let (program, exec_state, result) = match cache::read_old_ast().await {
Some(mut cached_state) => {
@ -618,6 +636,7 @@ impl ExecutorContext {
&self.settings,
Default::default(),
&mut cached_state.main.exec_state.id_generator,
grid_scale,
)
.await
.is_err()
@ -645,6 +664,7 @@ impl ExecutorContext {
&self.settings,
Default::default(),
&mut cached_state.main.exec_state.id_generator,
grid_scale,
)
.await
.is_err()
@ -689,6 +709,7 @@ impl ExecutorContext {
&self.settings,
Default::default(),
&mut cached_state.main.exec_state.id_generator,
grid_scale,
)
.await
.is_ok()
@ -1077,8 +1098,25 @@ impl ExecutorContext {
let _stats = crate::log::LogPerfStats::new("Interpretation");
// Re-apply the settings, in case the cache was busted.
let grid_scale = if self.settings.fixed_size_grid {
GridScaleBehavior::Fixed(
program
.meta_settings()
.ok()
.flatten()
.map(|s| s.default_length_units)
.map(kcmc::units::UnitLength::from),
)
} else {
GridScaleBehavior::ScaleWithZoom
};
self.engine
.reapply_settings(&self.settings, Default::default(), exec_state.id_generator())
.reapply_settings(
&self.settings,
Default::default(),
exec_state.id_generator(),
grid_scale,
)
.await
.map_err(KclErrorWithOutputs::no_outputs)?;

View File

@ -64,7 +64,7 @@ pub struct Settings {
}
/// Application wide settings.
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Validate)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Validate)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
pub struct AppSettings {
@ -94,6 +94,29 @@ pub struct AppSettings {
/// of the app to aid in development.
#[serde(default, skip_serializing_if = "is_default")]
pub show_debug_panel: bool,
/// If true, the grid cells will be fixed-size, where the width is the user's default length unit.
/// If false, the grid's size will scale as the user zooms in and out.
#[serde(default = "make_it_so")]
pub fixed_size_grid: bool,
}
/// Default to true.
fn make_it_so() -> bool {
true
}
impl Default for AppSettings {
fn default() -> Self {
Self {
appearance: Default::default(),
onboarding_status: Default::default(),
dismiss_web_banner: Default::default(),
stream_idle_mode: Default::default(),
allow_orbit_in_sketch_mode: Default::default(),
show_debug_panel: Default::default(),
fixed_size_grid: make_it_so(),
}
}
}
fn deserialize_stream_idle_mode<'de, D>(deserializer: D) -> Result<Option<u32>, D::Error>
@ -660,7 +683,7 @@ text_wrapping = true"#;
},
};
let parsed = toml::from_str::<Configuration>(settings_file).unwrap();
assert_eq!(parsed, expected,);
assert_eq!(parsed, expected);
// Write the file back out.
let serialized = toml::to_string(&parsed).unwrap();
@ -668,6 +691,7 @@ text_wrapping = true"#;
serialized,
r#"[settings.app]
onboarding_status = "dismissed"
fixed_size_grid = true
[settings.app.appearance]
theme = "dark"

View File

@ -141,6 +141,7 @@ pub async fn new_context(with_auth: bool, current_file: Option<PathBuf>) -> Resu
replay: None,
project_directory: None,
current_file: None,
fixed_size_grid: true,
};
if let Some(current_file) = current_file {
settings.with_current_file(crate::TypedPath(current_file));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Some files were not shown because too many files have changed in this diff Show More