From b701e22d28c3e1f9d6fea7f95f4ce72df0c9aab4 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Fri, 9 Oct 2026 19:08:36 +0300 Subject: [PATCH] Record a region profile polyline in its own coordinates The polyline round a region is written about the region's normal in its object coordinates with an identity transform, as the reference writes it; a rebuild from the history placed a polyline given in plane coordinates with a frame at the wrong place once the region moved. Arcs of one circle making a whole turn are recorded as that circle. Co-Authored-By: Claude Opus 5.5 --- src/scene/model/sweep_command_model.rs | 2 +- src/scene/model/sweep_model.rs | 45 +++++++++++++++----------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/scene/model/sweep_command_model.rs b/src/scene/model/sweep_command_model.rs index d6f3d0ba1..6b5f3dc43 100644 --- a/src/scene/model/sweep_command_model.rs +++ b/src/scene/model/sweep_command_model.rs @@ -674,7 +674,7 @@ pub fn assoc_source_supported(entity: &EntityType) -> bool { } /// The arbitrary-axis X direction of an entity normal. -fn arbitrary_x(normal: glam::DVec3) -> glam::DVec3 { +pub(crate) fn arbitrary_x(normal: glam::DVec3) -> glam::DVec3 { let world = if normal.x.abs() < 1.0 / 64.0 && normal.y.abs() < 1.0 / 64.0 { glam::DVec3::Y } else { glam::DVec3::Z }; world.cross(normal).normalize() } diff --git a/src/scene/model/sweep_model.rs b/src/scene/model/sweep_model.rs index 4c1c944a5..22badf863 100644 --- a/src/scene/model/sweep_model.rs +++ b/src/scene/model/sweep_model.rs @@ -662,24 +662,11 @@ pub fn embedded_path(entity: &EntityType) -> Option { } } -/// The frame placing a plane's coordinates in the world: its axes, normal -/// and origin as matrix columns. -fn plane_frame(plane: &Plane) -> Option<[f64; 16]> { - let normal = plane.normal()?; - let column = |v: [f64; 3], w: f64| glam::DVec4::new(v[0], v[1], v[2], w); - Some(glam::DMat4::from_cols( - column(plane.x_axis, 0.0), - column(plane.y_axis, 0.0), - column(normal, 0.0), - column(plane.origin, 1.0), - ) - .to_cols_array()) -} - /// A region of one boundary loop as the curve the reference records for it -/// as a profile: a lone circle or whole ellipse as itself, a loop of lines -/// and arcs as a closed polyline in the region's plane (with that plane's -/// frame), read from the region's modeler edges. +/// as a profile: a circle (one edge, or arcs of one circle making a whole +/// turn) or whole ellipse as itself, a loop of lines and arcs as a closed +/// polyline in its own coordinate system about the region's normal, read +/// from the region's modeler edges. pub fn region_boundary_entity(entity: &EntityType) -> Option<(EmbeddedEntity, [f64; 16])> { let EntityType::Region(region) = entity else { return None }; if !region.acis_data.has_data() { @@ -698,6 +685,19 @@ pub fn region_boundary_entity(entity: &EntityType) -> Option<(EmbeddedEntity, [f value.normal = normal; return Some((EmbeddedEntity::Circle(value), identity)); } + [Curve::Arc(first), rest @ ..] + if rest.iter().all(|piece| matches!(piece, Curve::Arc(arc) + if (arc.centre[0] - first.centre[0]).hypot(arc.centre[1] - first.centre[1]) <= 1e-9 * (1.0 + first.radius) + && (arc.radius - first.radius).abs() <= 1e-9 * (1.0 + first.radius))) + && (boundary.iter().map(|piece| match piece { Curve::Arc(arc) => arc.sweep(), _ => 0.0 }).sum::() + - std::f64::consts::TAU).abs() <= 1e-9 => + { + let mut value = codec::entities::Circle::new(); + value.center = world(plane.point_at(first.centre)); + value.radius = first.radius; + value.normal = normal; + return Some((EmbeddedEntity::Circle(value), identity)); + } [Curve::Ellipse(arc)] if (arc.sweep() - std::f64::consts::TAU).abs() <= 1e-9 => { let ellipse = arc.ellipse; let centre = Vec3::from(plane.point_at(ellipse.centre)); @@ -734,17 +734,24 @@ pub fn region_boundary_entity(entity: &EntityType) -> Option<(EmbeddedEntity, [f } else { first_start }; + // The polyline in its object coordinates: the arbitrary axes of the normal. + let axis_normal = glam::DVec3::new(normal.x, normal.y, normal.z); + let axis_x = super::sweep_command_model::arbitrary_x(axis_normal); + let axis_y = axis_normal.cross(axis_x); let mut polyline = LwPolyline::new(); + polyline.normal = normal; + polyline.elevation = glam::DVec3::from_array(plane.origin).dot(axis_normal); for piece in boundary { let (start, end, bulge) = ends(piece)?; let (from, to, bulge) = if close(start, at) { (start, end, bulge) } else if close(end, at) { (end, start, -bulge) } else { return None }; - let mut vertex = LwVertex::new(Vector2::new(from[0], from[1])); + let point = glam::DVec3::from_array(plane.point_at(from)); + let mut vertex = LwVertex::new(Vector2::new(point.dot(axis_x), point.dot(axis_y))); vertex.bulge = bulge; polyline.vertices.push(vertex); at = to; } polyline.is_closed = true; - Some((EmbeddedEntity::LwPolyline(polyline), plane_frame(&plane)?)) + Some((EmbeddedEntity::LwPolyline(polyline), identity)) } fn embedded_planar_entity(entity: &EntityType) -> Option<(EmbeddedEntity, [f64; 16])> {