diff --git a/crates/engine/src/cmd/blocks.rs b/crates/engine/src/cmd/blocks.rs
--- a/crates/engine/src/cmd/blocks.rs
+++ b/crates/engine/src/cmd/blocks.rs
@@ -36,7 +36,7 @@ pub fn specs() -> Vec<CommandSpec> {
         CommandSpec::new("insert", "Block...", run_insert)
             .menu(&["Insert", "Block..."])
             .alias(&["i", "-insert", "ddinsert"])
-            .params("{name (\"*NAME\": exploded), file?: DXF/DWG path (defines or redefines block `name`, default the file name, from its model space; base point $INSBASE), at: [x,y], scale?: number | [x, y], rotation? (degrees), basePoint?: [x,y] (block coordinates), attribs?: {TAG: value}, explode?: bool}")
+            .params("{name (\"*NAME\": exploded), file?: DXF/DWG path (defines or redefines block `name`, default the file name, from its model space; base point $INSBASE; its $INSUNITS converted to this drawing's on top of `scale`), at: [x,y], scale?: number | [x, y], rotation? (degrees), basePoint?: [x,y] (block coordinates), attribs?: {TAG: value}, explode?: bool}")
             .interactive(|_| Ok(Box::new(InsertM::default()))),
         CommandSpec::new("attdef", "Define Attributes...", run_attdef)
             .menu(&["Draw", "Block", "Define Attributes..."])
@@ -341,6 +341,8 @@ fn run_wblock(s: &mut Session, p: &Value) -> Result<Value> {
     d.text_styles = src.text_styles.clone();
     d.dim_styles = src.dim_styles.clone();
     d.blocks = src.blocks.clone();
+    // The file keeps the units of the drawing it came from, so INSERT can convert them back (#1996).
+    d.header.set_i64("INSUNITS", src.header.i64("INSUNITS", 1));
     // The base point becomes the written drawing's origin, its insertion base: by default 0,0 for
     // objects and the block's base point for a block (`base` is then in block coordinates).
     let base = point_param(p, "base");
@@ -579,6 +581,8 @@ struct InsertM {
     explode: bool,
     /// The file waiting for the Redefine answer.
     file: Option<insert_file::FileRef>,
+    /// A file's units converted to this drawing's, applied to the scale (None: not from a file).
+    unit_factor: Option<f64>,
 }
 
 impl InsertM {
@@ -595,7 +599,7 @@ impl InsertM {
             && self.values.len() >= self.attdefs.len()
             && let (Some(n), Some(a), Some(sc), Some(r)) = (self.name.clone(), self.at, self.scale, self.rotation)
         {
-            let h = insert_with(s, &n, a, sc, r, self.base, &self.values)?;
+            let h = insert_with(s, &n, a, sc * self.unit_factor.unwrap_or(1.0), r, self.base, &self.values)?;
             if self.explode {
                 insert_file::explode_insert(s, h)?;
             }
@@ -606,6 +610,7 @@ impl InsertM {
 
     /// Block `name` chosen: collect its attribute prompts and ask for the insertion point.
     fn choose(&mut self, s: &mut Session, name: &str) -> Result<()> {
+        self.unit_factor = None;
         let Some(b) = s.doc()?.block(name).cloned() else {
             s.echo(format!("Block \"{name}\" not found."));
             return Ok(());
@@ -633,7 +638,11 @@ impl InsertM {
     /// Define the block from a file, then choose it; a file that can't be read is reported.
     fn choose_file(&mut self, s: &mut Session, f: &insert_file::FileRef) -> Result<()> {
         match f.define(s) {
-            Ok(name) => self.choose(s, &name),
+            Ok((name, factor)) => {
+                self.choose(s, &name)?;
+                self.unit_factor = Some(factor);
+                Ok(())
+            }
             Err(e) => {
                 s.echo(e.to_string());
                 Ok(())
diff --git a/crates/engine/src/cmd/blocks/insert_file.rs b/crates/engine/src/cmd/blocks/insert_file.rs
--- a/crates/engine/src/cmd/blocks/insert_file.rs
+++ b/crates/engine/src/cmd/blocks/insert_file.rs
@@ -34,10 +34,52 @@ impl FileRef {
         !self.explicit && s.doc().is_ok_and(|d| d.block(&self.name).is_some())
     }
 
-    /// Read the file and (re)define its block. Returns the block's name.
-    pub fn define(&self, s: &mut Session) -> Result<String> {
+    /// Read the file and (re)define its block. Returns the block's name and the factor that
+    /// converts the file's `$INSUNITS` to this drawing's (1 when either declares no units).
+    pub fn define(&self, s: &mut Session) -> Result<(String, f64)> {
         let src = read_drawing(&self.path)?;
-        define_from(s, &src, &self.name)
+        let name = define_from(s, &src, &self.name)?;
+        Ok((name, unit_factor(&src, s.doc()?)))
+    }
+}
+
+/// Metres in one `$INSUNITS` unit (DXF Reference); `None` for 0 (unitless) and unknown codes.
+fn unit_metres(code: i64) -> Option<f64> {
+    Some(match code {
+        1 => 0.0254,
+        2 => 0.3048,
+        3 => 1609.344,
+        4 => 0.001,
+        5 => 0.01,
+        6 => 1.0,
+        7 => 1000.0,
+        8 => 2.54e-8,
+        9 => 2.54e-5,
+        10 => 0.9144,
+        11 => 1e-10,
+        12 => 1e-9,
+        13 => 1e-6,
+        14 => 0.1,
+        15 => 10.0,
+        16 => 100.0,
+        17 => 1e9,
+        18 => 1.495_978_707e11,
+        19 => 9.460_730_472_580_8e15,
+        20 => 3.085_677_581_491_4e16,
+        21 => 1200.0 / 3937.0,
+        22 => 1.0 / 39.37,
+        23 => 3600.0 / 3937.0,
+        24 => 6_336_000.0 / 3937.0,
+        _ => return None,
+    })
+}
+
+/// The scale that brings a file drawn in `src`'s units into `target`'s, as AutoCAD does (1 inch
+/// file into a metre drawing: 0.0254). A drawing without units is taken as is.
+fn unit_factor(src: &Drawing, target: &Drawing) -> f64 {
+    match (unit_metres(src.header.i64("INSUNITS", 0)), unit_metres(target.header.i64("INSUNITS", 0))) {
+        (Some(from), Some(to)) => from / to,
+        _ => 1.0,
     }
 }
 
@@ -230,7 +272,15 @@ pub(super) fn json_params(s: &mut Session, p: &Value) -> Result<Value> {
     }
     if let Some(path) = str_param(&p, "file").map(str::to_string) {
         let n = name.clone().filter(|n| !n.is_empty()).unwrap_or_else(|| stem(&path));
-        name = Some(FileRef { name: n, path, explicit: true }.define(s)?);
+        let (defined, factor) = FileRef { name: n, path, explicit: true }.define(s)?;
+        name = Some(defined);
+        if factor != 1.0 {
+            // The file's units to this drawing's, on top of the scale asked for.
+            p["scale"] = match p.get("scale") {
+                Some(Value::Array(a)) => Value::Array(a.iter().map(|v| v.as_f64().map_or_else(|| v.clone(), |x| json!(x * factor))).collect()),
+                other => json!(other.and_then(Value::as_f64).unwrap_or(1.0) * factor),
+            };
+        }
     }
     if let Some(n) = name {
         p["name"] = json!(n);
diff --git a/crates/engine/tests/insert_file.rs b/crates/engine/tests/insert_file.rs
--- a/crates/engine/tests/insert_file.rs
+++ b/crates/engine/tests/insert_file.rs
@@ -174,3 +174,31 @@ fn exploded_json_insert_is_one_undo_step() {
         assert_eq!(lines(&s), vec![(0.0, 0.0, 10.0, 0.0)], "{params}");
     }
 }
+
+/// #1997: a file drawn in inches comes into a drawing in metres at 0.0254, on top of the scale asked for.
+#[test]
+fn a_file_in_other_units_is_scaled_to_this_drawings_units() {
+    hooks();
+    let part = file("inches.dxf", "0,0");
+    let scale_of = |s: &Session| {
+        let d = s.doc().unwrap();
+        d.model.iter().rev().find_map(|e| if let EntityKind::Insert(i) = &e.kind { Some((i.scale.x, i.scale.y)) } else { None }).unwrap()
+    };
+    let near = |a: (f64, f64), b: (f64, f64)| (a.0 - b.0).abs() < 1e-12 && (a.1 - b.1).abs() < 1e-12;
+    let mut s = Session::new();
+    s.execute("units", &json!({ "insunits": 6 })).unwrap();
+    s.execute("insert", &json!({ "file": part, "at": [0, 0] })).unwrap();
+    assert!(near(scale_of(&s), (0.0254, 0.0254)), "{:?}", scale_of(&s));
+    s.execute("insert", &json!({ "file": part, "at": [0, 0], "scale": [2, 3] })).unwrap();
+    assert!(near(scale_of(&s), (0.0508, 0.0762)), "{:?}", scale_of(&s));
+    // Interactive INSERT converts too.
+    s.script(&format!("-INSERT inches={part} 0,0 1 1 0\n")).unwrap();
+    assert!(near(scale_of(&s), (0.0254, 0.0254)), "{:?}", scale_of(&s));
+    // A drawing in the same units, or without any, takes the file as is.
+    for units in [1, 0] {
+        let mut s = Session::new();
+        s.execute("units", &json!({ "insunits": units })).unwrap();
+        s.execute("insert", &json!({ "file": part, "at": [0, 0] })).unwrap();
+        assert!(near(scale_of(&s), (1.0, 1.0)), "INSUNITS {units}: {:?}", scale_of(&s));
+    }
+}
diff --git a/crates/engine/tests/wblock_units.rs b/crates/engine/tests/wblock_units.rs
new file mode 100644
--- /dev/null
+++ b/crates/engine/tests/wblock_units.rs
@@ -0,0 +1,25 @@
+//! WBLOCK writes the drawing's $INSUNITS, so INSERT of the written file, which converts the file's
+//! units to the drawing's (#1997), brings it back at its size (#1996).
+
+use cadcraft_engine::Session;
+use cadcraft_engine::cmd::file::{IoHooks, set_io};
+use cadcraft_engine::doc::EntityKind;
+use serde_json::json;
+
+#[test]
+fn a_wblock_file_from_a_metric_drawing_inserts_back_at_its_size() {
+    set_io(IoHooks {
+        read: |b, name| cadcraft_io::read(b, name).map_err(|e| e.to_string()),
+        write: |d, name| cadcraft_io::write(d, name).map_err(|e| e.to_string()),
+        plot: None,
+    });
+    let path = std::env::temp_dir().join(format!("cadcraft-wblock-units-{}.dxf", std::process::id())).to_string_lossy().into_owned();
+    let mut s = Session::new();
+    s.execute("units", &json!({ "insunits": 6 })).unwrap();
+    let h = s.execute("line", &json!({ "points": [[0, 0], [10, 0]] })).unwrap()["handles"][0].as_str().unwrap().to_string();
+    s.execute("wblock", &json!({ "handles": [h], "path": &path })).unwrap();
+    s.execute("insert", &json!({ "name": "PART", "file": &path, "at": [0, 0] })).unwrap();
+    let _ = std::fs::remove_file(&path);
+    let scale = s.doc().unwrap().model.iter().find_map(|e| if let EntityKind::Insert(i) = &e.kind { Some((i.scale.x, i.scale.y)) } else { None });
+    assert_eq!(scale, Some((1.0, 1.0)));
+}
