- Operation highlighting, body highlighting
This commit is contained in:
@@ -1949,7 +1949,7 @@ class OCCSketch(SketchInterface):
|
||||
|
||||
_SNAP_TOL: float = 1e-2 # world-unit tolerance for snapping line endpoints in loop detection
|
||||
|
||||
def _line_segments(self) -> List[Tuple[Tuple[float, float], Tuple[float, float]]]:
|
||||
def _line_segments(self) -> List[Tuple[float, float, float, float, int]]:
|
||||
"""Current line segments as world-coordinate tuples (uses solved positions).
|
||||
|
||||
Returns both straight line segments AND tessellated arc segments so
|
||||
@@ -1957,10 +1957,13 @@ class OCCSketch(SketchInterface):
|
||||
and external entities are excluded — they're reference geometry and
|
||||
must not affect the sketch profile.
|
||||
|
||||
Each segment is ``(x1, y1, x2, y2, entity_id)`` — the entity_id lets
|
||||
callers trace which sketch entity produced each segment.
|
||||
|
||||
Tessellation density: roughly 12 segments per π radians of arc sweep,
|
||||
which gives smooth-looking closed loops for face detection.
|
||||
"""
|
||||
segs: List[Tuple[Tuple[float, float], Tuple[float, float]]] = []
|
||||
segs: List[Tuple[float, float, float, float, int]] = []
|
||||
|
||||
# ── Straight line segments ──
|
||||
for line_id, (sid, eid2) in self._lines.items():
|
||||
@@ -1974,8 +1977,9 @@ class OCCSketch(SketchInterface):
|
||||
if s_ent and e_ent and s_ent.geometry and e_ent.geometry:
|
||||
segs.append(
|
||||
(
|
||||
(float(s_ent.geometry[0]), float(s_ent.geometry[1])),
|
||||
(float(e_ent.geometry[0]), float(e_ent.geometry[1])),
|
||||
float(s_ent.geometry[0]), float(s_ent.geometry[1]),
|
||||
float(e_ent.geometry[0]), float(e_ent.geometry[1]),
|
||||
line_id,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -2010,7 +2014,7 @@ class OCCSketch(SketchInterface):
|
||||
a2 = start_angle + t2 * sweep
|
||||
p1 = (cx + radius * math.cos(a1), cy + radius * math.sin(a1))
|
||||
p2 = (cx + radius * math.cos(a2), cy + radius * math.sin(a2))
|
||||
segs.append((p1, p2))
|
||||
segs.append((p1[0], p1[1], p2[0], p2[1], arc_id))
|
||||
|
||||
return segs
|
||||
|
||||
@@ -2018,8 +2022,8 @@ class OCCSketch(SketchInterface):
|
||||
"""Detect closed loops: polygon cycles from connected lines + each circle.
|
||||
|
||||
Each loop is one of:
|
||||
{"type": "polygon", "points": [(x,y), ...]} (closed, last == first)
|
||||
{"type": "circle", "center": (x,y), "radius": r}
|
||||
{"type": "polygon", "points": [(x,y), ...], "entity_ids": [int, ...]} (closed, last == first)
|
||||
{"type": "circle", "center": (x,y), "radius": r, "entity_ids": [int]}
|
||||
Line endpoint coordinates are snapped to ``_SNAP_TOL`` so a closed
|
||||
rectangle's four corners join into one cycle even after solver floating
|
||||
point jitter. Only connected components where every node has degree 2
|
||||
@@ -2035,11 +2039,13 @@ class OCCSketch(SketchInterface):
|
||||
|
||||
reprs: Dict[Any, Tuple[float, float]] = {} # key -> averaged world pt
|
||||
edges: List[Tuple[Any, Any]] = []
|
||||
for p1, p2 in segs:
|
||||
k1, k2 = key(p1), key(p2)
|
||||
reprs.setdefault(k1, p1)
|
||||
reprs.setdefault(k2, p2)
|
||||
edge_eids: Dict[Tuple[Any, Any], int] = {} # (k1,k2) -> entity_id
|
||||
for x1, y1, x2, y2, eid in segs:
|
||||
k1, k2 = key((x1, y1)), key((x2, y2))
|
||||
reprs.setdefault(k1, (x1, y1))
|
||||
reprs.setdefault(k2, (x2, y2))
|
||||
edges.append((k1, k2))
|
||||
edge_eids[(k1, k2) if k1 < k2 else (k2, k1)] = eid
|
||||
|
||||
# Undirected adjacency.
|
||||
adj: Dict[Any, List[Any]] = {}
|
||||
@@ -2066,8 +2072,9 @@ class OCCSketch(SketchInterface):
|
||||
if nb not in comp_seen:
|
||||
stack.append(nb)
|
||||
if all(len(adj[n]) == 2 for n in comp) and len(comp) >= 3:
|
||||
# Order the cycle by following each node's neighbor not yet visited.
|
||||
# Order the cycle by following each node's neighbour not yet visited.
|
||||
ordered: List[Any] = []
|
||||
eids: List[int] = []
|
||||
cur = comp[0]
|
||||
prev = None
|
||||
for _ in range(len(comp)):
|
||||
@@ -2075,16 +2082,26 @@ class OCCSketch(SketchInterface):
|
||||
nbrs = [nb for nb in adj[cur] if nb != prev]
|
||||
if not nbrs:
|
||||
break
|
||||
ekey = (cur, nbrs[0]) if cur < nbrs[0] else (nbrs[0], cur)
|
||||
if ekey in edge_eids:
|
||||
eids.append(edge_eids[ekey])
|
||||
prev = cur
|
||||
cur = nbrs[0]
|
||||
if len(ordered) == len(comp):
|
||||
pts = [reprs[k] for k in ordered]
|
||||
pts.append(pts[0])
|
||||
loops.append({"type": "polygon", "points": pts})
|
||||
loops.append(
|
||||
{"type": "polygon", "points": pts, "entity_ids": sorted(set(eids))}
|
||||
)
|
||||
seen |= comp_seen
|
||||
|
||||
# Circles are closed loops of their own.
|
||||
for cid, (center_id, r) in self._circles.items():
|
||||
circle_ent = self._entities.get(cid)
|
||||
if circle_ent is not None and circle_ent.is_construction:
|
||||
continue
|
||||
if cid in self._external_entity_ids:
|
||||
continue
|
||||
c_ent = self._entities.get(center_id)
|
||||
if c_ent and c_ent.geometry and r > 0:
|
||||
loops.append(
|
||||
@@ -2092,6 +2109,7 @@ class OCCSketch(SketchInterface):
|
||||
"type": "circle",
|
||||
"center": (float(c_ent.geometry[0]), float(c_ent.geometry[1])),
|
||||
"radius": float(r),
|
||||
"entity_ids": [cid],
|
||||
}
|
||||
)
|
||||
return loops
|
||||
@@ -2242,8 +2260,7 @@ class OCCSketch(SketchInterface):
|
||||
that is the rectangle minus the circle — exactly the
|
||||
"shape within a shape = closed without inner" behavior. A shape nested
|
||||
inside a hole (depth 2) becomes its own solid face again.
|
||||
|
||||
Returns a list of ``{"outer": loop, "holes": [loop, ...], "depth": int}``.
|
||||
Returns a list of ``{"outer": loop, "holes": [loop, ...], "depth": int, "entity_ids": [int, ...]}``.
|
||||
"""
|
||||
loops = self.get_closed_loops()
|
||||
if not loops:
|
||||
@@ -2267,7 +2284,11 @@ class OCCSketch(SketchInterface):
|
||||
# directly nested: depth one greater, and outer contains inner.
|
||||
if depths[j] == depths[i] + 1 and OCCSketch._loop_contains(inner, outer):
|
||||
holes.append(inner)
|
||||
faces.append({"outer": outer, "holes": holes, "depth": depths[i]})
|
||||
# Face entity_ids = union of outer + hole loop entity_ids.
|
||||
eids = set(outer.get("entity_ids", []))
|
||||
for h in holes:
|
||||
eids.update(h.get("entity_ids", []))
|
||||
faces.append({"outer": outer, "holes": holes, "depth": depths[i], "entity_ids": sorted(eids)})
|
||||
return faces
|
||||
|
||||
def find_face_at(self, x: float, y: float) -> Optional[Dict[str, Any]]:
|
||||
|
||||
Reference in New Issue
Block a user