From e2f5715de85ddec2ff4fed377724e7c1db64beae Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 22 Apr 2026 21:48:21 +0300 Subject: [PATCH] fix: correct arc sweep direction for reversed normals and mirrored blocks - Arc rendering now respects the normal vector: when normal.z < 0 the midpoint is placed on the clockwise side so the curve sweeps the right way - Removed spurious .to_radians() / .to_degrees() round-trips in arc.rs (angles are already stored in the unit the trig functions expect) - apply_grip and apply_transform use consistent angle units - normalize_insert_entity no longer converts arc angles; the old conversion was only needed by the now-removed .to_radians() call in to_truck - Added fix_mirrored_arc: swaps arc start/end angles when the INSERT has a mirrored scale (x_scale * y_scale < 0) so exploded arcs curve correctly Co-Authored-By: Claude Sonnet 4.6 --- src/entities/arc.rs | 36 +++++++++++++++------------ src/modules/home/modify/explode.rs | 39 +++++++++++++++++++----------- src/scene/mod.rs | 2 ++ 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/src/entities/arc.rs b/src/entities/arc.rs index c713ccb2..0fd0c9db 100644 --- a/src/entities/arc.rs +++ b/src/entities/arc.rs @@ -16,13 +16,17 @@ fn to_truck(arc: &Arc) -> TruckEntity { let cy = arc.center.y; let cz = arc.center.z; let r = arc.radius; - let sa = arc.start_angle.to_radians(); - let ea = arc.end_angle.to_radians(); - let mut end = ea; - if end < sa { - end += TAU; - } - let mid_a = sa + (end - sa) * 0.5; + let sa = arc.start_angle; + let ea = arc.end_angle; + // normal.z < 0 means the arc sweeps clockwise in the XY plane; + // place the midpoint on the CW side so the curve goes the right way. + let mid_a = if arc.normal.z < 0.0 { + let cw_span = if sa >= ea { sa - ea } else { sa - ea + TAU }; + sa - cw_span * 0.5 + } else { + let ccw_end = if ea >= sa { ea } else { ea + TAU }; + sa + (ccw_end - sa) * 0.5 + }; let p_start = Point3::new(cx + r * sa.cos(), cy + r * sa.sin(), cz); let p_end = Point3::new(cx + r * ea.cos(), cy + r * ea.sin(), cz); let p_mid = Point3::new(cx + r * mid_a.cos(), cy + r * mid_a.sin(), cz); @@ -56,8 +60,8 @@ fn grips(arc: &Arc) -> Vec { arc.center.z as f32, ); let r = arc.radius as f32; - let sa = (arc.start_angle as f32).to_radians(); - let ea = (arc.end_angle as f32).to_radians(); + let sa = arc.start_angle as f32; + let ea = arc.end_angle as f32; let ma = sa + angle_span(sa, ea) * 0.5; vec![ diamond_grip(0, ctr), @@ -111,16 +115,16 @@ fn apply_grip(arc: &mut Arc, grip_id: usize, apply: GripApply) { (1, GripApply::Absolute(p)) => { let dx = p.x - arc.center.x as f32; let dy = p.y - arc.center.y as f32; - arc.start_angle = (dy as f64).atan2(dx as f64).to_degrees(); + arc.start_angle = (dy as f64).atan2(dx as f64); } (2, GripApply::Absolute(p)) => { let dx = p.x - arc.center.x as f32; let dy = p.y - arc.center.y as f32; - arc.end_angle = (dy as f64).atan2(dx as f64).to_degrees(); + arc.end_angle = (dy as f64).atan2(dx as f64); } (3, GripApply::Translate(d)) => { - let sa = (arc.start_angle as f32).to_radians(); - let ea = (arc.end_angle as f32).to_radians(); + let sa = arc.start_angle as f32; + let ea = arc.end_angle as f32; let span = angle_span(sa, ea); let mid_a = sa + span * 0.5; let current_mid_x = arc.center.x as f32 + arc.radius as f32 * mid_a.cos(); @@ -148,10 +152,10 @@ fn apply_transform(arc: &mut Arc, t: &EntityTransform) { ); let dx = (p2.x - p1.x) as f64; let dy = (p2.y - p1.y) as f64; - let line_angle_deg = dy.atan2(dx).to_degrees(); + let line_angle = dy.atan2(dx); let tmp = entity.start_angle; - entity.start_angle = 2.0 * line_angle_deg - entity.end_angle; - entity.end_angle = 2.0 * line_angle_deg - tmp; + entity.start_angle = 2.0 * line_angle - entity.end_angle; + entity.end_angle = 2.0 * line_angle - tmp; }); } diff --git a/src/modules/home/modify/explode.rs b/src/modules/home/modify/explode.rs index 32f022ee..d2697c67 100644 --- a/src/modules/home/modify/explode.rs +++ b/src/modules/home/modify/explode.rs @@ -49,11 +49,14 @@ pub fn explode_entity(entity: &EntityType, document: &CadDocument) -> Vec explode_polyline2d(p), EntityType::Polyline(p) => explode_polyline(p), EntityType::Polyline3D(p) => explode_polyline3d(p), - EntityType::Insert(ins) => ins - .explode_from_document(document) - .into_iter() - .map(normalize_insert_entity) - .collect(), + EntityType::Insert(ins) => { + let is_mirrored = ins.x_scale() * ins.y_scale() < 0.0; + ins.explode_from_document(document) + .into_iter() + .map(normalize_insert_entity) + .map(|sub| fix_mirrored_arc(sub, is_mirrored)) + .collect() + } EntityType::MLine(ml) => explode_mline(ml), EntityType::Dimension(dim) => explode_dimension(dim), _ => vec![], @@ -140,10 +143,6 @@ fn explode_polyline2d(p: &Polyline2D) -> Vec { pub fn normalize_insert_entity(mut entity: EntityType) -> EntityType { match &mut entity { - EntityType::Arc(arc) => { - arc.start_angle = arc.start_angle.to_degrees(); - arc.end_angle = arc.end_angle.to_degrees(); - } EntityType::Ellipse(ell) => { let major_len = ell.major_axis_length(); let full_span = { @@ -170,14 +169,26 @@ pub fn normalize_insert_entity(mut entity: EntityType) -> EntityType { entity } -pub fn normalize_entity_for_block(mut entity: EntityType) -> EntityType { - if let EntityType::Arc(arc) = &mut entity { - arc.start_angle = arc.start_angle.to_radians(); - arc.end_angle = arc.end_angle.to_radians(); - } +pub fn normalize_entity_for_block(entity: EntityType) -> EntityType { entity } +/// Swap arc start/end angles when the INSERT that produced the entity was +/// mirrored (exactly one of x_scale / y_scale is negative). The acadrust +/// explode recalculates endpoint positions correctly but does not reverse the +/// sweep direction, so the arc would otherwise curve the wrong way. +pub fn fix_mirrored_arc(entity: EntityType, is_mirrored: bool) -> EntityType { + if !is_mirrored { + return entity; + } + if let EntityType::Arc(mut arc) = entity { + std::mem::swap(&mut arc.start_angle, &mut arc.end_angle); + EntityType::Arc(arc) + } else { + entity + } +} + fn explode_lwpolyline(p: &LwPolyline) -> Vec { let n = p.vertices.len(); if n < 2 { diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 45951ed1..e512bff7 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -518,11 +518,13 @@ impl Scene { } if let EntityType::Insert(ins) = e { + let is_mirrored = ins.x_scale() * ins.y_scale() < 0.0; return ins .explode_from_document(&self.document) .iter() .cloned() .map(crate::modules::home::modify::explode::normalize_insert_entity) + .map(|sub| crate::modules::home::modify::explode::fix_mirrored_arc(sub, is_mirrored)) .flat_map(|sub| { let (sub_color, sub_pattern_length, sub_pattern, sub_line_weight_px, sub_aci) = self.render_style(&sub);