diff --git a/tools/adaptive-isogrid/src/nx/extract_sandbox.py b/tools/adaptive-isogrid/src/nx/extract_sandbox.py index 8b4e61ee..b5291106 100644 --- a/tools/adaptive-isogrid/src/nx/extract_sandbox.py +++ b/tools/adaptive-isogrid/src/nx/extract_sandbox.py @@ -109,6 +109,7 @@ class EdgeSegment: # Arc-specific (None for lines) center_3d: Point3D | None = None radius: float | None = None + mid_3d: Point3D | None = None # midpoint sampled from NX edge (for arcs) # --------------------------------------------------------------------------- @@ -191,11 +192,29 @@ def _analyze_edge(edge: Any, lister: Any = None) -> EdgeSegment: pass if center is not None and radius is not None and radius > 0.0: + # Sample midpoint on the actual NX edge for reliable arc direction + mid_pt = None + try: + limits = eval_obj.AskLimits(evaluator) + t_mid = (float(limits[0]) + float(limits[1])) / 2.0 + result = eval_obj.EvaluateUnitVectors(evaluator, t_mid) + # Parse point from result + if hasattr(result, '__len__') and len(result) >= 1: + item = result[0] if hasattr(result[0], 'X') else result + if hasattr(item, 'X'): + mid_pt = (float(item.X), float(item.Y), float(item.Z)) + elif isinstance(item, (list, tuple)) and len(item) >= 3: + mid_pt = (float(item[0]), float(item[1]), float(item[2])) + if mid_pt is None and hasattr(result, 'X'): + mid_pt = (float(result.X), float(result.Y), float(result.Z)) + except Exception as exc2: + _log(f"[edge] Arc midpoint sampling failed: {exc2}") + _log(f"[edge] ARC: center=({center[0]:.3f},{center[1]:.3f},{center[2]:.3f}) " - f"r={radius:.3f}") + f"r={radius:.3f} mid={'OK' if mid_pt else 'NONE'}") return EdgeSegment( seg_type="arc", start_3d=p1, end_3d=p2, - center_3d=center, radius=radius, + center_3d=center, radius=radius, mid_3d=mid_pt, ) else: _log(f"[edge] IsArc=True but could not extract center/radius. " @@ -311,7 +330,7 @@ def _chain_edges_into_loops( found = True break elif pts_match(current_end, s.end_3d): - # Reversed edge — swap start/end + # Reversed edge — swap start/end (mid stays the same) used[i] = True reversed_seg = EdgeSegment( seg_type=s.seg_type, @@ -319,6 +338,7 @@ def _chain_edges_into_loops( end_3d=s.start_3d, center_3d=s.center_3d, radius=s.radius, + mid_3d=s.mid_3d, ) chain.append(reversed_seg) current_end = s.start_3d @@ -630,8 +650,12 @@ def _segments_to_json(segments: List[EdgeSegment], frame: LocalFrame) -> List[Di center_2d = _project_point_2d(seg.center_3d, frame) entry["center"] = [round(center_2d[0], 6), round(center_2d[1], 6)] entry["radius"] = round(seg.radius, 6) - # Determine clockwise/ccw: cross product of (start-center) × (end-center) - # projected onto the face normal + # Store the sampled midpoint (reliable arc direction, avoids cross-product + # degeneracy for 180° arcs) + if seg.mid_3d is not None: + mid_2d = _project_point_2d(seg.mid_3d, frame) + entry["mid"] = [round(mid_2d[0], 6), round(mid_2d[1], 6)] + # Also store clockwise as fallback (but mid is preferred) r1 = _sub(seg.start_3d, seg.center_3d) r2 = _sub(seg.end_3d, seg.center_3d) cross = _cross(r1, r2) diff --git a/tools/adaptive-isogrid/src/nx/import_profile.py b/tools/adaptive-isogrid/src/nx/import_profile.py index 86048082..ae702d82 100644 --- a/tools/adaptive-isogrid/src/nx/import_profile.py +++ b/tools/adaptive-isogrid/src/nx/import_profile.py @@ -364,28 +364,29 @@ def _draw_segment(part, seg, transform, lister): if seg_type == "arc" and "center" in seg: center_3d = unproject_point_to_3d(seg["center"], transform) radius = seg["radius"] - # Compute midpoint of arc for 3-point arc creation - cx, cy = seg["center"] - sx, sy = seg["start"] - ex, ey = seg["end"] - # Angles from center - sa = math.atan2(sy - cy, sx - cx) - ea = math.atan2(ey - cy, ex - cx) - clockwise = seg.get("clockwise", False) - # Compute mid-angle - if clockwise: - # CW: sa → ea going clockwise (decreasing angle) - da = sa - ea - if da <= 0: - da += 2 * math.pi - mid_angle = sa - da / 2.0 + + # Prefer the sampled midpoint from NX (avoids direction ambiguity) + if "mid" in seg: + mid_2d = seg["mid"] else: - # CCW: sa → ea going counter-clockwise (increasing angle) - da = ea - sa - if da <= 0: - da += 2 * math.pi - mid_angle = sa + da / 2.0 - mid_2d = [cx + radius * math.cos(mid_angle), cy + radius * math.sin(mid_angle)] + # Fallback: compute from clockwise flag + cx, cy = seg["center"] + sx, sy = seg["start"] + ex, ey = seg["end"] + sa = math.atan2(sy - cy, sx - cx) + ea = math.atan2(ey - cy, ex - cx) + clockwise = seg.get("clockwise", False) + if clockwise: + da = sa - ea + if da <= 0: + da += 2 * math.pi + mid_angle = sa - da / 2.0 + else: + da = ea - sa + if da <= 0: + da += 2 * math.pi + mid_angle = sa + da / 2.0 + mid_2d = [cx + radius * math.cos(mid_angle), cy + radius * math.sin(mid_angle)] mid_3d = unproject_point_to_3d(mid_2d, transform) try: _draw_arc_3pt(part, start_3d, mid_3d, end_3d)