diff --git a/tools/adaptive-isogrid/src/nx/extract_sandbox.py b/tools/adaptive-isogrid/src/nx/extract_sandbox.py index 710c05aa..522d7001 100644 --- a/tools/adaptive-isogrid/src/nx/extract_sandbox.py +++ b/tools/adaptive-isogrid/src/nx/extract_sandbox.py @@ -176,97 +176,63 @@ def _sample_edge_polyline(edge: Any, chord_tol_mm: float, lister: Any = None) -> uf = NXOpen.UF.UFSession.GetUFSession() - # Discover the correct method name (varies by NX version) - # UF_MODL_ask_curve_props → uf.Modl.AskCurveProps or similar - modl = uf.Modl - modl_methods = [m for m in dir(modl) if 'curve' in m.lower() or 'Curve' in m] - _log(f"[edge] UF.Modl curve methods: {modl_methods}") - eval_obj = uf.Eval - eval_methods = [m for m in dir(eval_obj) if not m.startswith('_')] - _log(f"[edge] UF.Eval methods: {eval_methods}") - - # Also check uf.Curve - try: - curve_obj = uf.Curve - curve_methods = [m for m in dir(curve_obj) if 'eval' in m.lower() or 'Eval' in m or 'prop' in m.lower()] - _log(f"[edge] UF.Curve eval methods: {curve_methods}") - except Exception: - pass - pts: List[Point3D] = [] parse_failures = 0 - # Try each possible API in order - eval_func = None - eval_style = None + try: + evaluator = eval_obj.Initialize2(edge.Tag) + limits = eval_obj.AskLimits(evaluator) + t0, t1 = float(limits[0]), float(limits[1]) - # Pattern A: uf.Modl.AskCurveProps(tag, param) — normalized 0..1 - for method_name in ('AskCurveProps', 'ask_curve_props'): - fn = getattr(modl, method_name, None) - if callable(fn): - eval_func = fn - eval_style = 'modl_props' - _log(f"[edge] Using uf.Modl.{method_name}") - break - - # Pattern B: uf.Eval.EvaluateUnitVectors or similar - if eval_func is None: - for method_name in eval_methods: - if 'valuat' in method_name.lower(): - eval_func = getattr(eval_obj, method_name) - eval_style = 'eval_' + method_name - _log(f"[edge] Using uf.Eval.{method_name}") - break - - # Pattern C: uf.Curve.EvaluateCurve(tag, param, deriv_flag) - if eval_func is None: + # Try arc-specific analytical approach first + is_arc_edge = False try: - curve_obj = uf.Curve - for method_name in ('EvaluateCurve', 'evaluate_curve'): - fn = getattr(curve_obj, method_name, None) - if callable(fn): - eval_func = fn - eval_style = 'curve_eval' - _log(f"[edge] Using uf.Curve.{method_name}") - break + is_arc_edge = eval_obj.IsArc(evaluator) except Exception: pass - if eval_func is None: - raise RuntimeError(f"No UF curve evaluation method found. Modl methods: {modl_methods}, Eval methods: {eval_methods}") - - try: - evaluator = None - if 'eval_' in (eval_style or ''): - evaluator = eval_obj.Initialize2(edge.Tag) - limits = eval_obj.AskLimits(evaluator) - t0, t1 = float(limits[0]), float(limits[1]) + if is_arc_edge: + # Get arc data and generate points analytically + try: + arc_data = eval_obj.AskArc(evaluator) + # arc_data is UFEval.Arc struct with: center, radius, etc. + # Extract what we can + _log(f"[edge] Arc data type: {type(arc_data).__name__}, attrs: {[a for a in dir(arc_data) if not a.startswith('_')]}") + # Try to access fields + center = None + radius = None + for attr in ('center', 'Center', 'arc_center'): + if hasattr(arc_data, attr): + center = getattr(arc_data, attr) + break + for attr in ('radius', 'Radius'): + if hasattr(arc_data, attr): + radius = float(getattr(arc_data, attr)) + break + if center is not None and radius is not None: + _log(f"[edge] Arc: center={center}, radius={radius}, t0={t0}, t1={t1}") + except Exception as exc: + _log(f"[edge] AskArc failed: {exc}") + # Use EvaluateUnitVectors for parametric sampling + # Signature: EvaluateUnitVectors(evaluator, param) → returns point + tangent + ... for i in range(n_pts + 1): - param = float(i) / float(n_pts) - - if eval_style == 'modl_props': - result = eval_func(edge.Tag, param) - elif eval_style == 'curve_eval': - # UF_CURVE_evaluate_curve(tag, natural_param, deriv_flag) → output array - # For arcs, natural param is in radians; for lines, arc length - # Use 0 = just point - t = t0 + (t1 - t0) * param if evaluator else param - result = eval_func(edge.Tag, t, 0) - else: - # eval_* method on evaluator - t = t0 + (t1 - t0) * param - result = eval_func(evaluator, 0, t) - - # Parse result — try multiple formats - pt = _parse_eval_point(result) - if pt is not None: - pts.append(pt) - else: + t = t0 + (t1 - t0) * (float(i) / float(n_pts)) + try: + result = eval_obj.EvaluateUnitVectors(evaluator, t) + # Parse result — could be tuple of (point, tangent, normal, binormal) + pt = _parse_eval_point(result) + if pt is not None: + pts.append(pt) + else: + parse_failures += 1 + if parse_failures <= 2: + _log(f"[edge] Parse failed at t={t:.4f}, type={type(result).__name__}, repr={repr(result)[:300]}") + except Exception as exc: parse_failures += 1 if parse_failures <= 2: - _log(f"[edge] Parse failed at param={param:.3f}, raw type={type(result).__name__}, repr={repr(result)[:200]}") + _log(f"[edge] EvaluateUnitVectors failed at t={t:.4f}: {exc}") finally: if evaluator is not None: try: @@ -275,12 +241,12 @@ def _sample_edge_polyline(edge: Any, chord_tol_mm: float, lister: Any = None) -> pass if len(pts) >= 2: - _log(f"[edge] sampled via {eval_style} ({len(pts)} pts, {parse_failures} failures)") + _log(f"[edge] sampled via EvaluateUnitVectors ({len(pts)} pts, {parse_failures} failures)") return pts - _log(f"[edge] {eval_style} insufficient points ({len(pts)}), falling back") + _log(f"[edge] EvaluateUnitVectors insufficient points ({len(pts)}), falling back") except Exception as exc: - _log(f"[edge] UF curve eval failed: {exc}") + _log(f"[edge] UF Eval failed: {exc}") # 2) Fallback: IBaseCurve.Evaluate (signature differs by NX versions) try: