fix: handle closed circular edges (holes) - UF.Eval + GetLength circle fallback + debug logging

This commit is contained in:
2026-02-16 17:57:06 +00:00
parent 98d510154d
commit 23b6fe855b

View File

@@ -96,7 +96,7 @@ def unproject_to_3d(points2d: Sequence[Point2D], frame: LocalFrame) -> List[Poin
# NX edge sampling
# ---------------------------------------------------------------------------
def _sample_edge_polyline(edge: Any, chord_tol_mm: float) -> List[Point3D]:
def _sample_edge_polyline(edge: Any, chord_tol_mm: float, lister: Any = None) -> List[Point3D]:
"""
Sample an NX edge as a polyline.
@@ -117,47 +117,62 @@ def _sample_edge_polyline(edge: Any, chord_tol_mm: float) -> List[Point3D]:
except Exception as exc:
raise RuntimeError(f"Edge.GetVertices() failed: {exc}")
# Check if edge is straight (linear) — vertices are sufficient
try:
edge_type = edge.SolidEdgeType
# EdgeType values: Linear=1, Circular=2, Elliptical=3, etc.
is_linear = (str(edge_type) == "EdgeType.Linear" or int(edge_type) == 1)
except Exception:
is_linear = False
# Check edge type
is_linear = False
is_circular = False
is_closed = (_norm(_sub(p1, p2)) < 0.001) # closed edge = start == end
if is_linear:
try:
edge_type_str = str(edge.SolidEdgeType)
is_linear = "Linear" in edge_type_str
is_circular = "Circular" in edge_type_str
except Exception:
pass
if lister and (is_closed or is_circular):
lister.WriteLine(f"[edge] type={edge_type_str if 'edge_type_str' in dir() else '?'} closed={is_closed} circ={is_circular} p1={p1}")
if is_linear and not is_closed:
return [p1, p2]
# For curved edges: try to evaluate intermediate points
# Method: use NXOpen.Session.GetSession().GetUFSession() for UF curve evaluation
# For curved/closed edges: try UF_EVAL for parametric evaluation
try:
import NXOpen
session = NXOpen.Session.GetSession()
uf = session.GetUFSession()
edge_tag = edge.Tag
# Get edge length
# Get edge length for point density
try:
length = edge.GetLength()
except Exception:
length = _norm(_sub(p2, p1))
length = _norm(_sub(p2, p1)) if not is_closed else 50.0 # estimate
n_pts = max(2, int(length / max(chord_tol_mm, 0.1)))
n_pts = max(8, int(length / max(chord_tol_mm, 0.1)))
if is_closed:
n_pts = max(24, n_pts) # circles need enough points
# UF_EVAL: initialize evaluator, get points along curve
# UF_EVAL approach
evaluator = uf.Eval.Initialize2(edge_tag)
limits = uf.Eval.AskLimits(evaluator)
t0, t1 = limits[0], limits[1]
t0 = limits[0]
t1 = limits[1]
pts: List[Point3D] = []
for i in range(n_pts + 1):
t = t0 + (t1 - t0) * (i / n_pts)
# UF_EVAL_evaluate returns (point[3], derivatives[3], ...)
# The output format depends on the derivative order requested
result = uf.Eval.Evaluate(evaluator, 0, t)
# result is typically a tuple/array with (x, y, z, ...)
if hasattr(result, '__len__') and len(result) >= 3:
pts.append((float(result[0]), float(result[1]), float(result[2])))
else:
break
# Try different result formats
if isinstance(result, (list, tuple)):
if len(result) >= 3:
pts.append((float(result[0]), float(result[1]), float(result[2])))
elif len(result) == 1 and hasattr(result[0], '__len__'):
r = result[0]
pts.append((float(r[0]), float(r[1]), float(r[2])))
elif hasattr(result, 'X'):
pts.append((float(result.X), float(result.Y), float(result.Z)))
uf.Eval.Free(evaluator)
@@ -166,7 +181,43 @@ def _sample_edge_polyline(edge: Any, chord_tol_mm: float) -> List[Point3D]:
except Exception:
pass
# Last fallback: just vertices (straight-line approximation of curve)
# Fallback for circular closed edges: try to get arc data from UF
if is_circular and is_closed:
try:
import NXOpen
session = NXOpen.Session.GetSession()
uf = session.GetUFSession()
# UF_CURVE_ask_arc_data returns (arc_center, radius, angles...)
arc_data = uf.Curve.AskArcData(edge.Tag)
# arc_data typically: (matrix_tag, start_angle, end_angle, center[3], radius)
# or it could be structured differently
# Generate circle points manually if we can extract center + radius
except Exception:
pass
# Fallback for closed edges: generate a small circle around the vertex
# This is wrong geometrically but at least provides a visual marker
if is_closed:
try:
length = edge.GetLength()
radius = length / (2.0 * math.pi)
# Generate circle in XY plane around vertex
# (will be projected to 2D later, so orientation matters)
pts = []
n = 24
for i in range(n + 1):
angle = 2.0 * math.pi * i / n
px = p1[0] + radius * math.cos(angle)
py = p1[1] + radius * math.sin(angle)
pz = p1[2]
pts.append((px, py, pz))
return pts
except Exception:
pass
# Last resort: vertices only
return [p1, p2]
@@ -239,7 +290,7 @@ def _chain_edges_into_loops(
p_start, p_end, edge = segments[start_idx]
# Sample this edge
edge_pts = _sample_edge_polyline(edge, chord_tol_mm=0.5)
edge_pts = _sample_edge_polyline(edge, chord_tol_mm=0.5, lister=lister)
chain_pts.extend(edge_pts)
chain_edges.append(edge)
@@ -260,7 +311,7 @@ def _chain_edges_into_loops(
continue
if pts_match(current_end, s1):
used[i] = True
edge_pts = _sample_edge_polyline(e, chord_tol_mm=0.5)
edge_pts = _sample_edge_polyline(e, chord_tol_mm=0.5, lister=lister)
chain_pts.extend(edge_pts[1:]) # skip duplicate junction point
chain_edges.append(e)
current_end = s2
@@ -269,7 +320,7 @@ def _chain_edges_into_loops(
elif pts_match(current_end, s2):
# Edge is reversed — traverse backward
used[i] = True
edge_pts = _sample_edge_polyline(e, chord_tol_mm=0.5)
edge_pts = _sample_edge_polyline(e, chord_tol_mm=0.5, lister=lister)
edge_pts.reverse()
chain_pts.extend(edge_pts[1:])
chain_edges.append(e)