fix: handle closed circular edges (holes) - UF.Eval + GetLength circle fallback + debug logging
This commit is contained in:
@@ -96,7 +96,7 @@ def unproject_to_3d(points2d: Sequence[Point2D], frame: LocalFrame) -> List[Poin
|
|||||||
# NX edge sampling
|
# 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.
|
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:
|
except Exception as exc:
|
||||||
raise RuntimeError(f"Edge.GetVertices() failed: {exc}")
|
raise RuntimeError(f"Edge.GetVertices() failed: {exc}")
|
||||||
|
|
||||||
# Check if edge is straight (linear) — vertices are sufficient
|
# Check edge type
|
||||||
try:
|
is_linear = False
|
||||||
edge_type = edge.SolidEdgeType
|
is_circular = False
|
||||||
# EdgeType values: Linear=1, Circular=2, Elliptical=3, etc.
|
is_closed = (_norm(_sub(p1, p2)) < 0.001) # closed edge = start == end
|
||||||
is_linear = (str(edge_type) == "EdgeType.Linear" or int(edge_type) == 1)
|
|
||||||
except Exception:
|
|
||||||
is_linear = False
|
|
||||||
|
|
||||||
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]
|
return [p1, p2]
|
||||||
|
|
||||||
# For curved edges: try to evaluate intermediate points
|
# For curved/closed edges: try UF_EVAL for parametric evaluation
|
||||||
# Method: use NXOpen.Session.GetSession().GetUFSession() for UF curve evaluation
|
|
||||||
try:
|
try:
|
||||||
import NXOpen
|
import NXOpen
|
||||||
session = NXOpen.Session.GetSession()
|
session = NXOpen.Session.GetSession()
|
||||||
uf = session.GetUFSession()
|
uf = session.GetUFSession()
|
||||||
edge_tag = edge.Tag
|
edge_tag = edge.Tag
|
||||||
|
|
||||||
# Get edge length
|
# Get edge length for point density
|
||||||
try:
|
try:
|
||||||
length = edge.GetLength()
|
length = edge.GetLength()
|
||||||
except Exception:
|
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)
|
evaluator = uf.Eval.Initialize2(edge_tag)
|
||||||
limits = uf.Eval.AskLimits(evaluator)
|
limits = uf.Eval.AskLimits(evaluator)
|
||||||
t0, t1 = limits[0], limits[1]
|
t0 = limits[0]
|
||||||
|
t1 = limits[1]
|
||||||
|
|
||||||
pts: List[Point3D] = []
|
pts: List[Point3D] = []
|
||||||
for i in range(n_pts + 1):
|
for i in range(n_pts + 1):
|
||||||
t = t0 + (t1 - t0) * (i / n_pts)
|
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 = uf.Eval.Evaluate(evaluator, 0, t)
|
||||||
# result is typically a tuple/array with (x, y, z, ...)
|
# Try different result formats
|
||||||
if hasattr(result, '__len__') and len(result) >= 3:
|
if isinstance(result, (list, tuple)):
|
||||||
pts.append((float(result[0]), float(result[1]), float(result[2])))
|
if len(result) >= 3:
|
||||||
else:
|
pts.append((float(result[0]), float(result[1]), float(result[2])))
|
||||||
break
|
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)
|
uf.Eval.Free(evaluator)
|
||||||
|
|
||||||
@@ -166,7 +181,43 @@ def _sample_edge_polyline(edge: Any, chord_tol_mm: float) -> List[Point3D]:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
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]
|
return [p1, p2]
|
||||||
|
|
||||||
|
|
||||||
@@ -239,7 +290,7 @@ def _chain_edges_into_loops(
|
|||||||
p_start, p_end, edge = segments[start_idx]
|
p_start, p_end, edge = segments[start_idx]
|
||||||
|
|
||||||
# Sample this edge
|
# 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_pts.extend(edge_pts)
|
||||||
chain_edges.append(edge)
|
chain_edges.append(edge)
|
||||||
|
|
||||||
@@ -260,7 +311,7 @@ def _chain_edges_into_loops(
|
|||||||
continue
|
continue
|
||||||
if pts_match(current_end, s1):
|
if pts_match(current_end, s1):
|
||||||
used[i] = True
|
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_pts.extend(edge_pts[1:]) # skip duplicate junction point
|
||||||
chain_edges.append(e)
|
chain_edges.append(e)
|
||||||
current_end = s2
|
current_end = s2
|
||||||
@@ -269,7 +320,7 @@ def _chain_edges_into_loops(
|
|||||||
elif pts_match(current_end, s2):
|
elif pts_match(current_end, s2):
|
||||||
# Edge is reversed — traverse backward
|
# Edge is reversed — traverse backward
|
||||||
used[i] = True
|
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()
|
edge_pts.reverse()
|
||||||
chain_pts.extend(edge_pts[1:])
|
chain_pts.extend(edge_pts[1:])
|
||||||
chain_edges.append(e)
|
chain_edges.append(e)
|
||||||
|
|||||||
Reference in New Issue
Block a user