MEASUREINIT: choose the units of new drawings, kept in the profile (#1792)

* MEASUREINIT: choose the units of new drawings, kept in the profile

NEW, QNEW and the first drawing at startup were always imperial unless a
template or {"metric": true} was given, and nothing kept a metric choice
across restarts. MEASUREINIT (0 imperial, the default; 1 metric) is now a
profile variable: NEW without `metric` or a template follows it, the app's
startup drawing follows it, and Options > Files shows it as "Units of new
drawings". QNEWTEMPLATE and an explicit `metric` still win.

Part of #1182.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dxn7PD9WzP4ET2LU7BRLDK

* contributors: add Richard Sauer (aussieboer61)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dxn7PD9WzP4ET2LU7BRLDK

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
Richard Sauer
2026-10-24 01:54:51 -04:00
committed by GitHub
co-authored by Claude Opus 5.5 Brandon Thomas
parent 7db656fdc7
commit fde62a39ab
9 changed files with 86 additions and 3 deletions
+3 -1
View File
@@ -357,7 +357,9 @@ fn main() -> eframe::Result {
app.open_path(f);
}
if app.session.docs.is_empty() {
app.session.new_drawing(false);
// In the units the profile asks for (MEASUREINIT, restored above).
let metric = app.session.settings.measureinit;
app.session.new_drawing(metric);
}
let ctx = cc.egui_ctx.clone();
let desktop_theme = desktop_theme::DesktopTheme::start(move || ctx.request_repaint());
+4
View File
@@ -80,3 +80,7 @@ sync_github_name = true
real_name = "Rodrigo Gressler"
display_name = "Rodrigo"
sync_github_name = false
[people.aussieboer61]
real_name = "Richard Sauer"
display_name = "Richard Sauer"
+3 -2
View File
@@ -163,7 +163,7 @@ pub fn specs() -> Vec<CommandSpec> {
.menu(&["File", "New Drawing..."])
.key("Cmd+N")
.alias(&["qnew"])
.params("{metric?: bool, template?: path to a .dwt/.dxf template (\".\" for none; default QNEWTEMPLATE)}")
.params("{metric?: bool (default MEASUREINIT), template?: path to a .dwt/.dxf template (\".\" for none; default QNEWTEMPLATE)}")
.enabled(always)
.noundo(),
CommandSpec::new("open", "Open...", run_open)
@@ -252,7 +252,8 @@ fn run_new(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(r) = template::run_new(s, p)? {
return Ok(r);
}
let i = s.new_drawing(bool_or(p, "metric", false));
let metric = s.settings.measureinit;
let i = s.new_drawing(bool_or(p, "metric", metric));
Ok(json!({ "index": i, "title": s.docs.get(i).map(|d| d.title.clone()) }))
}
+1
View File
@@ -52,6 +52,7 @@ pub const PROFILE_VARS: &[&str] = &[
"PICKADD",
"PICKDRAG",
"MAXARRAY",
"MEASUREINIT",
];
/// The most profile entries [`restore`] reads (a saved profile is untrusted input).
+4
View File
@@ -359,6 +359,9 @@ pub struct Settings {
pub gridmajor: u32,
/// ZOOMFACTOR: how far one mouse wheel step zooms (3..=100).
pub zoomfactor: u32,
/// MEASUREINIT: the units of a new drawing made without a template (NEW, QNEW and the first
/// drawing at startup): `false` (0, the default) imperial, `true` (1) metric.
pub measureinit: bool,
/// -VIEW sEttings > Layer snapshot: named views save the layer settings with them.
pub view_layer_snapshot: bool,
pub dynmode: bool,
@@ -477,6 +480,7 @@ impl Default for Settings {
gridunit: Vec2::new(0.5, 0.5),
gridmajor: 5,
zoomfactor: 60,
measureinit: false,
view_layer_snapshot: true,
dynmode: true,
dyndigrip: 31,
+3
View File
@@ -64,6 +64,7 @@ const SESSION_VARS: &[&str] = &[
"CURSORSIZE",
"MAXARRAY",
"ZOOMFACTOR",
"MEASUREINIT",
"LASTPOINT",
"FONTALT",
"FONTFALLBACK",
@@ -140,6 +141,7 @@ pub fn get(s: &Session, name: &str) -> Option<Value> {
"CURSORSIZE" => json!(st.cursorsize),
"MAXARRAY" => json!(st.maxarray),
"ZOOMFACTOR" => json!(st.zoomfactor),
"MEASUREINIT" => json!(i32::from(st.measureinit)),
"LASTPOINT" => json!([s.last_point.x, s.last_point.y, 0.0]),
// Held in radians (`$ANGBASE` in DXF is in degrees); reported in degrees, like POLARANG.
"ANGBASE" => json!(s.doc().ok()?.header.f64("ANGBASE", 0.0).to_degrees()),
@@ -355,6 +357,7 @@ pub fn set(s: &mut Session, name: &str, v: &Value) -> Result<()> {
"CURSORSIZE" => st.cursorsize = as_f64(v).ok_or_else(bad)?.clamp(1.0, 100.0),
"MAXARRAY" => st.maxarray = as_f64(v).ok_or_else(bad)?.clamp(100.0, 10_000_000.0) as u64,
"ZOOMFACTOR" => st.zoomfactor = as_i(v).filter(|z| (3..=100).contains(z)).ok_or_else(bad)? as u32,
"MEASUREINIT" => st.measureinit = small(v, 1).ok_or_else(bad)? == 1,
"FONTALT" => cadcraft_fonts::ttf::set_font_alt(v.as_str().ok_or_else(bad)?),
"FONTFALLBACK" => cadcraft_fonts::ttf::set_fallback_fonts(v.as_str().ok_or_else(bad)?),
"SAVETIME" => crate::autosave::set_savetime(
+51
View File
@@ -0,0 +1,51 @@
//! MEASUREINIT: the units of a new drawing made without a template (issue #1182). It is a profile
//! setting, so a metric choice survives restarts; an explicit `metric` or template still wins.
use cadcraft_engine::Session;
use serde_json::json;
fn var(s: &mut Session, name: &str) -> serde_json::Value {
s.execute("getvar", &json!({ "name": name })).unwrap()["value"].clone()
}
#[test]
fn imperial_by_default() {
let mut s = Session::empty();
assert_eq!(var(&mut s, "MEASUREINIT"), json!(0));
s.execute("new", &json!({})).unwrap();
assert_eq!(var(&mut s, "INSUNITS"), json!(1));
assert_eq!(var(&mut s, "MEASUREMENT"), json!(0));
}
#[test]
fn new_follows_measureinit() {
let mut s = Session::empty();
s.execute("options", &json!({ "MEASUREINIT": 1 })).unwrap();
s.execute("new", &json!({})).unwrap();
assert_eq!(var(&mut s, "INSUNITS"), json!(4));
assert_eq!(var(&mut s, "MEASUREMENT"), json!(1));
assert_eq!(var(&mut s, "DIMSTYLE"), json!("ISO-25"));
// An explicit choice still wins.
s.execute("new", &json!({ "metric": false })).unwrap();
assert_eq!(var(&mut s, "INSUNITS"), json!(1));
}
#[test]
fn only_zero_or_one() {
let mut s = Session::empty();
for bad in [json!(2), json!(-1), json!("metric")] {
assert!(s.execute("options", &json!({ "MEASUREINIT": bad })).is_err(), "{bad}");
}
assert_eq!(var(&mut s, "MEASUREINIT"), json!(0));
}
#[test]
fn kept_in_the_profile() {
let mut s = Session::empty();
s.execute("options", &json!({ "MEASUREINIT": 1 })).unwrap();
let saved = cadcraft_engine::cmd::options::profile(&s);
assert_eq!(saved["MEASUREINIT"], json!(1));
let mut restarted = Session::empty();
cadcraft_engine::cmd::options::restore(&mut restarted, &saved);
assert_eq!(var(&mut restarted, "MEASUREINIT"), json!(1));
}
+4
View File
@@ -980,6 +980,10 @@
Reset keeps the interface language. Скидання не змінює мову інтерфейсу.
Reset to Defaults Скинути до типових
Save new drawings as Зберігати нові креслення як
Units of new drawings Одиниці нових креслень
Imperial (inches) Імперські (дюйми)
Metric (millimetres) Метричні (міліметри)
New and the first drawing at startup use these units unless a default template is set (QNEWTEMPLATE). Команда «Створити» і перше креслення під час запуску використовують ці одиниці, якщо не задано типовий шаблон (QNEWTEMPLATE).
Screen magnifiers, screen readers and recorders follow the system cursor. Екранні лупи, програми читання з екрана та запису стежать за системним курсором.
Select objects first, then choose a command Спочатку вибирати об'єкти, потім команду
Selection Вибір
1 # CADCraft Ukrainian interface catalog — original translations by @dmatviichuk.
980
981
982
983
984
985
986
987
988
989
+13
View File
@@ -233,6 +233,18 @@ fn files(app: &CadApp, ui: &mut egui::Ui, changes: &mut Map<String, Value>) {
}
ui.end_row();
ui.label(crate::tl!("Units of new drawings"));
ui.horizontal(|ui| {
let metric = app.session.settings.measureinit;
if ui.radio(!metric, crate::tl!("Imperial (inches)")).clicked() && metric {
changes.insert("MEASUREINIT".into(), json!(0));
}
if ui.radio(metric, crate::tl!("Metric (millimetres)")).clicked() && !metric {
changes.insert("MEASUREINIT".into(), json!(1));
}
});
ui.end_row();
ui.label(crate::tl!("Automatic save every"));
let mut minutes = cadcraft_engine::autosave::savetime();
let max = cadcraft_engine::autosave::MAX_SAVETIME;
@@ -258,6 +270,7 @@ fn files(app: &CadApp, ui: &mut egui::Ui, changes: &mut Map<String, Value>) {
}
ui.end_row();
});
hint(ui, crate::tl!("New and the first drawing at startup use these units unless a default template is set (QNEWTEMPLATE)."));
hint(ui, crate::tl!("A drawing opened from a file keeps that file's format."));
hint(ui, crate::tl!("Drawings with no file yet are saved in the default version. 0 minutes turns automatic save off."));
}