View twist: read and write every twist within one turn, so #2022's hostile-angle checks hold

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.
This commit is contained in:
Tanish Manku
2026-11-03 14:58:37 -07:00
parent 58bb1e065e
commit 162cc4792b
5 changed files with 10 additions and 8 deletions
+1 -1
View File
@@ -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),
+2 -2
View File
@@ -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);
+5 -3
View File
@@ -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.
+1 -1
View File
@@ -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));
+1 -1
View File
@@ -136,7 +136,7 @@ fn write_tables(w: &mut W, plan: TableWritePlan<'_>) -> HashMap<String, String>
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);