fix(extract): use EvaluateUnitVectors for parametric edge sampling

Available NXOpen.UF.Eval methods discovered:
- EvaluateUnitVectors(evaluator, t) - parametric point+tangent
- AskArc(evaluator) - arc center/radius for circular edges
- Initialize2, AskLimits, Free - evaluator lifecycle

Also logs arc data attributes for debugging.
This commit is contained in:
2026-02-17 01:36:25 +00:00
parent fbdafb9a37
commit cd7f7e8aa9

View File

@@ -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: