diff --git a/src/scene/page_setup.rs b/src/scene/page_setup.rs index af3926e75..193264d13 100644 --- a/src/scene/page_setup.rs +++ b/src/scene/page_setup.rs @@ -175,7 +175,23 @@ impl Scene { let ObjectType::Dictionary(d) = self.document.objects.get(&dh)? else { return None; }; - d.entries.iter().find(|(k, _)| k == name).map(|(_, h)| *h) + d.entries + .iter() + .find(|(k, _)| k.eq_ignore_ascii_case(name)) + .map(|(_, h)| *h) + } + + /// The dictionary's own spelling of `name` (page-setup names compare + /// case-insensitively, like every named-object dictionary key). + fn page_setup_key(&self, name: &str) -> Option { + let dh = self.plotsettings_dict_handle()?; + let ObjectType::Dictionary(d) = self.document.objects.get(&dh)? else { + return None; + }; + d.entries + .iter() + .find(|(k, _)| k.eq_ignore_ascii_case(name)) + .map(|(k, _)| k.clone()) } /// Create or update the named page setup from `ps` (its `page_name` and @@ -183,7 +199,9 @@ impl Scene { /// are inserted and registered in the dictionary. pub fn page_setup_save(&mut self, name: &str, mut ps: PlotSettings) { let dict_handle = self.ensure_plotsettings_dict(); - ps.page_name = name.to_string(); + // Replacing keeps the entry's stored spelling so the dictionary key + // and the object's page_name stay one name. + ps.page_name = self.page_setup_key(name).unwrap_or_else(|| name.to_string()); ps.owner = dict_handle; if let Some(h) = self.page_setup_handle(name) { ps.handle = h; @@ -206,8 +224,12 @@ impl Scene { let handle = if let Some(ObjectType::Dictionary(d)) = self.document.objects.get_mut(&dict_handle) { - let h = d.entries.iter().find(|(k, _)| k == name).map(|(_, h)| *h); - d.entries.retain(|(k, _)| k != name); + let h = d + .entries + .iter() + .find(|(k, _)| k.eq_ignore_ascii_case(name)) + .map(|(_, h)| *h); + d.entries.retain(|(k, _)| !k.eq_ignore_ascii_case(name)); h } else { None @@ -223,7 +245,8 @@ impl Scene { if old == new || new.trim().is_empty() { return; } - if self.page_setup_handle(new).is_some() { + // A different spelling of the same name is a rename, not a collision. + if !old.eq_ignore_ascii_case(new) && self.page_setup_handle(new).is_some() { return; // name collision } let Some(dict_handle) = self.plotsettings_dict_handle() else { @@ -231,7 +254,7 @@ impl Scene { }; let mut renamed = None; if let Some(ObjectType::Dictionary(d)) = self.document.objects.get_mut(&dict_handle) { - if let Some(e) = d.entries.iter_mut().find(|(k, _)| k == old) { + if let Some(e) = d.entries.iter_mut().find(|(k, _)| k.eq_ignore_ascii_case(old)) { e.0 = new.to_string(); renamed = Some(e.1); } diff --git a/src/scene/parametric_constraints.rs b/src/scene/parametric_constraints.rs index 0dc086c20..0ab068550 100644 --- a/src/scene/parametric_constraints.rs +++ b/src/scene/parametric_constraints.rs @@ -244,6 +244,9 @@ pub(crate) fn set_polyline_vertex( x: f64, y: f64, ) -> bool { + let Some(index) = polyline_vertex_index(entity, index) else { + return false; + }; match entity { acadrust::EntityType::LwPolyline(polyline) => polyline .vertices @@ -800,9 +803,27 @@ pub(crate) fn resolve_point(entity: &acadrust::EntityType, marker: i32) -> Optio if marker < 0 { return None; } - super::dimension_assoc::source_points(entity) - .get(marker as usize) - .copied() + let points = super::dimension_assoc::source_points(entity); + // The closing segment of a closed polyline runs back to vertex 0, so its + // end marker (one past the last vertex) names that vertex — the same wrap + // the solver's `line_segment` applies. + let index = match polyline_vertex_index(entity, marker as usize) { + Some(index) => index, + None => marker as usize, + }; + points.get(index).copied() +} + +/// `marker` as a vertex index of a polyline, wrapping the closing segment's +/// end (one past the last vertex) onto vertex 0 when the polyline is closed. +/// `None` for anything that is not a 2D polyline. +pub(crate) fn polyline_vertex_index(entity: &acadrust::EntityType, marker: usize) -> Option { + let (count, closed) = match entity { + acadrust::EntityType::LwPolyline(polyline) => (polyline.vertices.len(), polyline.is_closed), + acadrust::EntityType::Polyline2D(polyline) => (polyline.vertices.len(), polyline.is_closed()), + _ => return None, + }; + Some(if closed && marker == count && count > 0 { 0 } else { marker }) } /// Below this squared distance (1e-6 world units), two points count as diff --git a/src/scene/parametric_solve.rs b/src/scene/parametric_solve.rs index a0c7a1792..59a5d30ca 100644 --- a/src/scene/parametric_solve.rs +++ b/src/scene/parametric_solve.rs @@ -87,8 +87,16 @@ impl EntityGeom { (EntityGeom::Line(l), 0) => Some(l.p1), (EntityGeom::Line(l), 1) => Some(l.p2), (EntityGeom::Ray(l), 0) => Some(l.p1), - (EntityGeom::Polyline { points, .. }, marker) if marker >= 0 => { - points.get(marker as usize).copied() + (EntityGeom::Polyline { points, closed, .. }, marker) if marker >= 0 => { + // The closing segment's end marker (one past the last vertex) + // is vertex 0 on a closed polyline, as in `line_segment`. + let index = marker as usize; + let index = if *closed && index == points.len() && !points.is_empty() { + 0 + } else { + index + }; + points.get(index).copied() } (EntityGeom::Circle(c), -3) => Some(c.center), (EntityGeom::Arc(a), 0) => Some(a.start),