diff --git a/src/app/automation.rs b/src/app/automation.rs index 9d230d30c..088493ab7 100644 --- a/src/app/automation.rs +++ b/src/app/automation.rs @@ -1801,7 +1801,7 @@ mod tests { viewport.id = 2; viewport.width = 100.0; viewport.height = 50.0; - viewport.status.is_on = true; + viewport.turn_on(); scene.add_entity(codec::EntityType::Viewport(viewport)); for entity in scene.document.entities_mut() { if let codec::EntityType::Viewport(viewport) = entity { diff --git a/src/app/command_driver/tests.rs b/src/app/command_driver/tests.rs index c5a250660..85e6e3c8b 100644 --- a/src/app/command_driver/tests.rs +++ b/src/app/command_driver/tests.rs @@ -935,14 +935,17 @@ mod delobj_tests { expected, ); } - assert_eq!( - sweep_source_presence(2, crate::command::ExtrudeMode::Surface), - (false, false), - ); - assert_eq!( - sweep_source_presence(3, crate::command::ExtrudeMode::Surface), - (false, true), - ); + // A swept surface is associative by default (SURFACEASSOCIATIVITY 1) + // and keeps the profile and path it follows, whatever DELOBJ says; + // DELOBJ reaches surfaces only with associativity off, a profile-wide + // switch this parallel test leaves alone. + assert!(crate::scene::model::sweep_model::surface_associativity()); + for value in [2, 3] { + assert_eq!( + sweep_source_presence(value, crate::command::ExtrudeMode::Surface), + (true, true), + ); + } } } diff --git a/src/app/commands/view.rs b/src/app/commands/view.rs index 3c6bc31dd..1436e1450 100644 --- a/src/app/commands/view.rs +++ b/src/app/commands/view.rs @@ -719,7 +719,7 @@ impl OpenCADStudio { vp.view_height, vp.height, ), - vp.status.is_on, + vp.is_on(), vp.status.locked, )) } else { diff --git a/src/app/viewport_dimension_tests.rs b/src/app/viewport_dimension_tests.rs index 04e09759f..2628f1355 100644 --- a/src/app/viewport_dimension_tests.rs +++ b/src/app/viewport_dimension_tests.rs @@ -27,7 +27,7 @@ fn fixture() -> (OpenCADStudio, Handle, ViewportFrame) { viewport.view_height = 1000.0; viewport.custom_scale = 0.1; viewport.view_direction = Vector3::UNIT_Z; - viewport.status.is_on = true; + viewport.turn_on(); let viewport = scene.add_entity(EntityType::Viewport(viewport)); let frame = scene.viewport_frame(viewport).unwrap(); scene.document.header.dimension_associativity = 2; @@ -484,7 +484,7 @@ fn viewport_dimension_eligibility_is_shared_and_uses_clipping() { first.viewport ); if let Some(EntityType::Viewport(vp)) = scene.document.get_entity_mut(first.viewport) { - vp.status.is_on = false; + vp.turn_off(); } assert!(scene.viewport_frames_at_paper_point(paper).is_empty()); assert!(scene.dimension_pick_through_viewport(paper, 0.2).is_none()); diff --git a/src/entities/viewport.rs b/src/entities/viewport.rs index 3e65cca64..37798bef2 100644 --- a/src/entities/viewport.rs +++ b/src/entities/viewport.rs @@ -113,7 +113,7 @@ fn properties(vp: &Viewport) -> Vec { field: "vp_on", value: PropValue::BoolToggle { field: "vp_on", - value: vp.status.is_on, + value: vp.is_on(), }, }, ro( @@ -193,11 +193,14 @@ fn apply_geom_prop(vp: &mut Viewport, field: &str, value: &str) { return; } "vp_on" => { - vp.status.is_on = if value == "toggle" { - !vp.status.is_on + // turn_on/turn_off keep the explicit-off bit in step, so a + // viewport the file marked off can be turned back on. + let on = if value == "toggle" { !vp.is_on() } else { value == "true" }; + if on { + vp.turn_on(); } else { - value == "true" - }; + vp.turn_off(); + } return; } "vp_perspective" => { diff --git a/src/io/mod.rs b/src/io/mod.rs index 0ad3565bc..7ba12b2c0 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -2539,7 +2539,7 @@ fn fix_viewport_status_flags(doc: &mut CadDocument) { // If bit 0 is not set but bit 15 is, this is an external-format viewport: // treat bit 15 as "on" and bit 14 as "locked". if (bits & 0x0001) == 0 && (bits & 0x8000) != 0 { - vp.status.is_on = true; + vp.turn_on(); vp.status.locked = (bits & 0x4000) != 0; } } diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 2949edd90..bb92874c9 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -7504,7 +7504,7 @@ impl Scene { let Some(EntityType::Viewport(viewport)) = self.document.get_entity(handle) else { continue; }; - if viewport.common.owner_handle != paper_block || !viewport.status.is_on { + if viewport.common.owner_handle != paper_block || !viewport.is_on() { continue; } let mode = render_mode_override.unwrap_or(viewport.render_mode); diff --git a/src/scene/model/sweep_command_model.rs b/src/scene/model/sweep_command_model.rs index babc1824a..5d68a3994 100644 --- a/src/scene/model/sweep_command_model.rs +++ b/src/scene/model/sweep_command_model.rs @@ -475,8 +475,10 @@ pub fn assoc_edge_curve(entity: &EntityType) -> Option<(i32, codec::objects::Ass EntityType::Arc(value) => { let normal = glam::DVec3::new(value.normal.x, value.normal.y, value.normal.z).try_normalize()?; let axis = arbitrary_x(normal); - let mut end = value.end_angle; - while end <= value.start_angle { end += std::f64::consts::TAU; } + // The end past the start by the arc's sweep; an equal pair is a + // full turn. (A loop adding TAU never ends on a huge angle.) + let sweep = (value.end_angle - value.start_angle).rem_euclid(std::f64::consts::TAU); + let end = value.start_angle + if sweep > 0.0 { sweep } else { std::f64::consts::TAU }; Some((11, K::Arc, arc(glam::DVec3::new(value.center.x, value.center.y, value.center.z), normal, axis, value.radius, value.start_angle, end))) } EntityType::LwPolyline(polyline) => { diff --git a/src/scene/model/sweep_model.rs b/src/scene/model/sweep_model.rs index dcdc21641..fb5f12072 100644 --- a/src/scene/model/sweep_model.rs +++ b/src/scene/model/sweep_model.rs @@ -13,7 +13,7 @@ use codec::EntityType; use crate::entities::curve::entity_curve; pub use super::sweep_command_model::{ - is_sweep_path, is_sweep_profile, scaled_sweep_path_refusal, spatial_profile, spatial_sweep_record, surface_sweep_record, sweep_expressions, link_sweep_expressions, link_sweep_sources, sweep_sources, resweep_from_sources, surface_associativity, set_surface_associativity, assoc_edge_curve, sweep_record, sweep_refusal_code, sweep_selection_options, swept_surface_entity, swept_with_options, + is_sweep_path, is_sweep_profile, scaled_sweep_path_refusal, spatial_profile, spatial_sweep_record, surface_sweep_record, sweep_expressions, link_sweep_expressions, link_sweep_sources, sweep_sources, SWEEP_SOURCES_APP, resweep_from_sources, surface_associativity, set_surface_associativity, assoc_edge_curve, sweep_record, sweep_refusal_code, sweep_selection_options, swept_surface_entity, swept_with_options, }; /// A drawn profile, as the kernel wants it: the plane it lies in and the diff --git a/src/scene/modify.rs b/src/scene/modify.rs index 23d798a0b..dbde57ece 100644 --- a/src/scene/modify.rs +++ b/src/scene/modify.rs @@ -1140,8 +1140,13 @@ impl Scene { .filter(|(_, kind)| !matches!(kind, crate::scene::ChangeKind::Removed)) .map(|(handle, _)| *handle) .collect::>(); - if changed.is_empty() { return Vec::new(); } + // Every edit passes through here: a drawing with no associative + // swept surface (no application registered for them) skips the scan. + if changed.is_empty() || !self.document.app_ids.contains(sweep_model::SWEEP_SOURCES_APP) { + return Vec::new(); + } let linked = self.document.entities() + .filter(|entity| matches!(entity, EntityType::Surface(_))) .filter_map(|entity| { let handle = entity.common().handle; let (profile, path) = sweep_model::sweep_sources(entity)?; diff --git a/src/scene/mspace.rs b/src/scene/mspace.rs index 2d4a0b305..de7e10b5a 100644 --- a/src/scene/mspace.rs +++ b/src/scene/mspace.rs @@ -801,7 +801,7 @@ impl Scene { let Some(EntityType::Viewport(vp)) = self.document.get_entity(*handle) else { return None; }; - if !vp.status.is_on { + if !vp.is_on() { return None; } let rect = self.viewport_screen_rect(vp.common.handle, canvas)?; @@ -838,7 +838,7 @@ impl Scene { pub fn viewport_displays_content(&self, vp_handle: Handle) -> bool { matches!( self.document.get_entity(vp_handle), - Some(EntityType::Viewport(vp)) if vp.status.is_on + Some(EntityType::Viewport(vp)) if vp.is_on() ) } @@ -850,7 +850,7 @@ impl Scene { let Some(EntityType::Viewport(vp)) = self.document.get_entity(*handle) else { return None; }; - if vp.status.is_on { + if vp.is_on() { Some(vp.common.handle) } else { None diff --git a/src/scene/paper.rs b/src/scene/paper.rs index 11aa7b34d..669d6f74c 100644 --- a/src/scene/paper.rs +++ b/src/scene/paper.rs @@ -194,7 +194,7 @@ impl Scene { let Some(EntityType::Viewport(vp)) = self.document.get_entity(handle) else { continue; }; - if !vp.status.is_on + if !vp.is_on() || self.entity_temporarily_hidden(handle) { continue; diff --git a/src/scene/project.rs b/src/scene/project.rs index 765c6169a..0cf433927 100644 --- a/src/scene/project.rs +++ b/src/scene/project.rs @@ -21,7 +21,7 @@ impl Scene { }) .filter(|vp| { vp.common.owner_handle == paper_block - && vp.status.is_on + && vp.is_on() && only_vp.map_or(true, |h| vp.common.handle == h) && exclude_vp.map_or(true, |h| vp.common.handle != h) }) @@ -439,7 +439,7 @@ impl Scene { .iter() .filter_map(|handle| match self.document.get_entity(*handle) { Some(EntityType::Viewport(viewport)) - if viewport.common.owner_handle == paper_block && viewport.status.is_on => + if viewport.common.owner_handle == paper_block && viewport.is_on() => { Some(viewport) }