fix: arc direction — sample midpoint from NX edge instead of cross-product
Cross product of (start-center) × (end-center) is zero for 180° arcs, causing random clockwise assignment. Now samples actual midpoint via UF Eval at t_mid, stores as 'mid' in JSON. Import prefers 'mid' over computed clockwise direction.
This commit is contained in:
@@ -109,6 +109,7 @@ class EdgeSegment:
|
|||||||
# Arc-specific (None for lines)
|
# Arc-specific (None for lines)
|
||||||
center_3d: Point3D | None = None
|
center_3d: Point3D | None = None
|
||||||
radius: float | 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
|
pass
|
||||||
|
|
||||||
if center is not None and radius is not None and radius > 0.0:
|
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}) "
|
_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(
|
return EdgeSegment(
|
||||||
seg_type="arc", start_3d=p1, end_3d=p2,
|
seg_type="arc", start_3d=p1, end_3d=p2,
|
||||||
center_3d=center, radius=radius,
|
center_3d=center, radius=radius, mid_3d=mid_pt,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
_log(f"[edge] IsArc=True but could not extract center/radius. "
|
_log(f"[edge] IsArc=True but could not extract center/radius. "
|
||||||
@@ -311,7 +330,7 @@ def _chain_edges_into_loops(
|
|||||||
found = True
|
found = True
|
||||||
break
|
break
|
||||||
elif pts_match(current_end, s.end_3d):
|
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
|
used[i] = True
|
||||||
reversed_seg = EdgeSegment(
|
reversed_seg = EdgeSegment(
|
||||||
seg_type=s.seg_type,
|
seg_type=s.seg_type,
|
||||||
@@ -319,6 +338,7 @@ def _chain_edges_into_loops(
|
|||||||
end_3d=s.start_3d,
|
end_3d=s.start_3d,
|
||||||
center_3d=s.center_3d,
|
center_3d=s.center_3d,
|
||||||
radius=s.radius,
|
radius=s.radius,
|
||||||
|
mid_3d=s.mid_3d,
|
||||||
)
|
)
|
||||||
chain.append(reversed_seg)
|
chain.append(reversed_seg)
|
||||||
current_end = s.start_3d
|
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)
|
center_2d = _project_point_2d(seg.center_3d, frame)
|
||||||
entry["center"] = [round(center_2d[0], 6), round(center_2d[1], 6)]
|
entry["center"] = [round(center_2d[0], 6), round(center_2d[1], 6)]
|
||||||
entry["radius"] = round(seg.radius, 6)
|
entry["radius"] = round(seg.radius, 6)
|
||||||
# Determine clockwise/ccw: cross product of (start-center) × (end-center)
|
# Store the sampled midpoint (reliable arc direction, avoids cross-product
|
||||||
# projected onto the face normal
|
# 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)
|
r1 = _sub(seg.start_3d, seg.center_3d)
|
||||||
r2 = _sub(seg.end_3d, seg.center_3d)
|
r2 = _sub(seg.end_3d, seg.center_3d)
|
||||||
cross = _cross(r1, r2)
|
cross = _cross(r1, r2)
|
||||||
|
|||||||
@@ -364,28 +364,29 @@ def _draw_segment(part, seg, transform, lister):
|
|||||||
if seg_type == "arc" and "center" in seg:
|
if seg_type == "arc" and "center" in seg:
|
||||||
center_3d = unproject_point_to_3d(seg["center"], transform)
|
center_3d = unproject_point_to_3d(seg["center"], transform)
|
||||||
radius = seg["radius"]
|
radius = seg["radius"]
|
||||||
# Compute midpoint of arc for 3-point arc creation
|
|
||||||
cx, cy = seg["center"]
|
# Prefer the sampled midpoint from NX (avoids direction ambiguity)
|
||||||
sx, sy = seg["start"]
|
if "mid" in seg:
|
||||||
ex, ey = seg["end"]
|
mid_2d = seg["mid"]
|
||||||
# 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
|
|
||||||
else:
|
else:
|
||||||
# CCW: sa → ea going counter-clockwise (increasing angle)
|
# Fallback: compute from clockwise flag
|
||||||
da = ea - sa
|
cx, cy = seg["center"]
|
||||||
if da <= 0:
|
sx, sy = seg["start"]
|
||||||
da += 2 * math.pi
|
ex, ey = seg["end"]
|
||||||
mid_angle = sa + da / 2.0
|
sa = math.atan2(sy - cy, sx - cx)
|
||||||
mid_2d = [cx + radius * math.cos(mid_angle), cy + radius * math.sin(mid_angle)]
|
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)
|
mid_3d = unproject_point_to_3d(mid_2d, transform)
|
||||||
try:
|
try:
|
||||||
_draw_arc_3pt(part, start_3d, mid_3d, end_3d)
|
_draw_arc_3pt(part, start_3d, mid_3d, end_3d)
|
||||||
|
|||||||
Reference in New Issue
Block a user