From 162cc4792b4855aa242e5176d284d4e764618460 Mon Sep 17 00:00:00 2001 From: Tanish Manku Date: Tue, 3 Nov 2026 14:58:37 -0700 Subject: [PATCH] View twist: read and write every twist within one turn, so #2022's hostile-angle checks hold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A twist of -1e-20 degrees read as a full turn (2π), and a huge twist set from JSON or MCP was written as a huge angle. Readers and writers of VIEWPORT 51, VPORT 51 and VIEW 50 now go through norm_angle. --- crates/io/src/dxf_read/entities.rs | 2 +- crates/io/src/dxf_read/tables.rs | 4 ++-- crates/io/src/dxf_write/entity.rs | 8 +++++--- crates/io/src/dxf_write/layout.rs | 2 +- crates/io/src/dxf_write/sections.rs | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/io/src/dxf_read/entities.rs b/crates/io/src/dxf_read/entities.rs index 7027e4750..6f547c145 100644 --- a/crates/io/src/dxf_read/entities.rs +++ b/crates/io/src/dxf_read/entities.rs @@ -245,7 +245,7 @@ pub(super) fn entity_with_table_budget(kind: &str, tags: &[Tag], table_cells_lef "ACAD_TABLE" => EntityKind::Table(acad_table(tags, table_cells_left)), "VIEWPORT" => EntityKind::Viewport({ // View twist (group 51, degrees). - let twist = t.f(51).filter(|a| a.is_finite()).map_or(0.0, |a| a.rem_euclid(360.0).to_radians()); + let twist = t.f(51).map_or(0.0, |a| cadcraft_geom::norm_angle(a.to_radians())); Viewport { center: t.p(10), width: t.fd(40, 1.0), diff --git a/crates/io/src/dxf_read/tables.rs b/crates/io/src/dxf_read/tables.rs index 6bc9e6178..518df8d3f 100644 --- a/crates/io/src/dxf_read/tables.rs +++ b/crates/io/src/dxf_read/tables.rs @@ -224,7 +224,7 @@ pub(super) fn tables(tags: &[Tag], d: &mut Drawing, rx: &mut Rx) { if name.is_empty() || !(h.is_finite() && w.is_finite() && h > 0.0 && w > 0.0) || d.views.len() >= MAX_OBJECTS { continue; } - let twist = t.f(50).filter(|a| a.is_finite()).map_or(0.0, |a| a.rem_euclid(360.0).to_radians()); + let twist = t.f(50).map_or(0.0, |a| cadcraft_geom::norm_angle(a.to_radians())); // The view centre 10/20 is in display coordinates, whose origin is the target 12/22/32. let center = t.view_center(10, 12, twist); if !(center.x.is_finite() && center.y.is_finite()) { @@ -326,7 +326,7 @@ const MAX_VPORT_TILES: usize = 64; fn vport_record(d: &mut Drawing, t: &T) { let name = t.s(2).map(|n| n.trim().to_string()).unwrap_or_default(); // View twist (group 51, degrees). - let twist = t.f(51).filter(|a| a.is_finite()).map_or(0.0, |a| a.rem_euclid(360.0).to_radians()); + let twist = t.f(51).map_or(0.0, |a| cadcraft_geom::norm_angle(a.to_radians())); // The view centre 12/22 is in display coordinates, whose origin is the view target 17/27/37. let (min, max, center) = (t.p(10).xy(), t.p(11).xy(), t.view_center(12, 17, twist)); let height = t.fd(40, 0.0); diff --git a/crates/io/src/dxf_write/entity.rs b/crates/io/src/dxf_write/entity.rs index 94a626f42..5d759e223 100644 --- a/crates/io/src/dxf_write/entity.rs +++ b/crates/io/src/dxf_write/entity.rs @@ -684,12 +684,14 @@ fn write_viewport_entity(w: &mut W, d: &Drawing, e: &Entity, owner: &str, paper: // Status 68: a positive value is "on" (the stacking order; 1 is the active viewport), 0 off. w.i(68, if v.on { i64::from(v.id.clamp(1, 32767)) } else { 0 }); w.i(69, i64::from(v.id)); + // Within one turn, so a hostile twist (from JSON or MCP) writes a sane angle. + let twist = cadcraft_geom::norm_angle(v.view_twist()); // The view centre in display coordinates (turned by the twist). - w.p2(12, dxf_ext::model_center_to_dcs(v.view_center, v.view_twist())); + w.p2(12, dxf_ext::model_center_to_dcs(v.view_center, twist)); w.f(45, v.view_height); // View twist (degrees), when the viewport has one. - if v.view_twist() != 0.0 { - w.f(51, v.view_twist().to_degrees()); + if twist != 0.0 { + w.f(51, twist.to_degrees()); } // Status flags: 16384 = display locked, 32768 = always set, 65536 = clipped by the // 340 entity, 131072 = off. diff --git a/crates/io/src/dxf_write/layout.rs b/crates/io/src/dxf_write/layout.rs index 8a5c180dd..07698fb88 100644 --- a/crates/io/src/dxf_write/layout.rs +++ b/crates/io/src/dxf_write/layout.rs @@ -95,7 +95,7 @@ pub(super) fn vport_table(w: &mut W, d: &Drawing, ext: Bounds2) { w.i(70, 0); w.p2(10, finite(t.min, Vec2::ZERO)); w.p2(11, finite(t.max, Vec2::new(1.0, 1.0))); - let twist = if t.twist.is_finite() { t.twist } else { 0.0 }; + let twist = cadcraft_geom::norm_angle(t.twist); w.p2(12, dxf_ext::model_center_to_dcs(finite(t.center, Vec2::ZERO), twist)); // Snap grid origin and rotation (SNAPBASE, SNAPANG in degrees): R13+ files keep them here. w.p2(13, d.header.point("SNAPBASE").map(|p| p.xy()).filter(|p| p.is_finite()).unwrap_or(Vec2::ZERO)); diff --git a/crates/io/src/dxf_write/sections.rs b/crates/io/src/dxf_write/sections.rs index 295d063e6..e80adb408 100644 --- a/crates/io/src/dxf_write/sections.rs +++ b/crates/io/src/dxf_write/sections.rs @@ -136,7 +136,7 @@ fn write_tables(w: &mut W, plan: TableWritePlan<'_>) -> HashMap w.s(2, &v.name); w.i(70, i64::from(v.layout.is_some())); w.f(40, v.height); - let twist = if v.twist.is_finite() { v.twist } else { 0.0 }; + let twist = cadcraft_geom::norm_angle(v.twist); w.p2(10, dxf_ext::model_center_to_dcs(v.center, twist)); w.f(41, v.width); w.p(11, Vec3::Z);