Fiexed highlighting of operations

This commit is contained in:
bklronin
2026-08-19 00:11:24 +02:00
parent b184ade967
commit 6b6f7de5ab
4 changed files with 155 additions and 206 deletions
+6 -2
View File
@@ -1426,14 +1426,18 @@ class OCCRenderer(Renderer):
ais.SetColor(Quantity_Color(*color, Quantity_TOC_RGB))
ais.SetDisplayMode(1) # shaded
try:
ais.SetTransparency(0.65)
ais.SetTransparency(0.2)
except Exception:
logger.debug("op highlight transparency set failed", exc_info=True)
try:
ais.SetSelectability(0)
except Exception:
logger.debug("op highlight selectability set failed", exc_info=True)
try:
ais.SetPolygonOffsets(3, 1.0, -0.5)
except Exception:
logger.debug("op highlight polygon offset failed", exc_info=True)
self._context.Display(ais, True)
self._context.Display(ais, False)
self._op_highlight_ais = ais
if self._view is not None:
self._view.Redraw()
+104 -12
View File
@@ -8,7 +8,7 @@ import os
from datetime import datetime
from typing import Any, Callable, Dict, List, Optional, Tuple
from PySide6.QtCore import Qt, Slot, QSize, QSettings
from PySide6.QtCore import Qt, Slot, QSize, QSettings, QTimer
from PySide6.QtGui import (
QAction,
QColor,
@@ -1383,6 +1383,10 @@ def _replay_body_features(
"skipping chamfer"
)
continue
geom = kernel.chamfer(geom, feat.radius, edges=edges)
if geom is None:
return None
continue
if feat.operation in ("array", "pattern"):
# Pattern needs no sketch — it repeats the running solid.
@@ -1719,9 +1723,12 @@ class MainWindow(QMainWindow):
# selectable axis / centre / plane from the 3D viewport.
self._sketch_gizmo_selection: Optional[dict] = None
self._selected_body: Optional[Body] = None
# Track which body we're hiding while an operation-history highlight
# is active, so we can restore it when the selection changes.
self._op_highlight_body_id: Optional[str] = None
# Auto-clears the operation-history highlight 1 s after it is shown so
# the 3D view returns to its normal, fully-selectable state.
self._op_highlight_timer = QTimer(self)
self._op_highlight_timer.setSingleShot(True)
self._op_highlight_timer.timeout.connect(self._on_op_highlight_timeout)
# Body-highlight state: when no operation is selected we tint the
# selected body light-blue; save its original colour to restore later.
self._body_highlight_id: Optional[str] = None
@@ -2685,6 +2692,12 @@ class MainWindow(QMainWindow):
"""
if feat.operation in ("cut", "union"):
return self._compute_tool_shape(body, features, index, feat)
# Fillet / chamfer: show a cube covering the corner where the op
# was applied, not the whole body.
if feat.operation in ("fillet", "chamfer"):
shape = self._compute_fillet_bbox_shape(body, features, index, feat)
if shape is not None:
return shape
# Default: show the intermediate body after this operation.
geom = _replay_body_features(
self._kernel, body, features[: index + 1],
@@ -2748,6 +2761,80 @@ class MainWindow(QMainWindow):
return None
return self._kernel._get_shape(tool_geom)
def _compute_fillet_bbox_shape(
self, body: Body, features: List[Feature], index: int, feat: Feature
) -> Optional[Any]:
"""Return a cube covering where a fillet/chamfer op was applied.
Replays the body *before* the op, resolves the edges the op
touches (the same face-key / fingerprint resolution the replay
uses), and returns a box around their bounding volume a corner
marker, not the whole body. Returns *None* when the op spans
the whole body (scope "all") or no edges resolve, so the caller
falls back to the intermediate body.
"""
# Scope "all" fillets every edge of the body — a corner cube is
# meaningless there, so let the caller show the whole body.
if feat.scope == "all":
return None
try:
pre_geom = _replay_body_features(
self._kernel, body, features[:index],
self._through_all_length_for_geometry,
component=self._current_component,
)
except Exception:
logger.debug("fillet bbox replay failed", exc_info=True)
return None
pre = self._kernel._get_shape(pre_geom) if pre_geom is not None else None
if pre is None:
return None
# Resolve the fillet/chamfer edges on the pre-op geometry, exactly
# as the replay does (FaceKey first, fingerprint fallback).
edges: List[Any] = []
if feat.face_keys is not None:
edges = _resolve_edges_by_face_keys(
pre, feat, self._current_component
) or []
if not edges:
edges = _resolve_edges_by_fingerprint(pre, feat.edge_refs)
if not edges:
return None
from OCP.BRep import BRep_Builder
from OCP.Bnd import Bnd_Box
from OCP.BRepBndLib import BRepBndLib
from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox
from OCP.TopoDS import TopoDS_Compound
from OCP.gp import gp_Pnt
comp = TopoDS_Compound()
builder = BRep_Builder()
builder.MakeCompound(comp)
for e in edges:
builder.Add(comp, e)
bbox = Bnd_Box()
BRepBndLib.AddClose_s(comp, bbox)
if bbox.IsVoid():
return None
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
# Pad by the fillet radius so the cube comfortably covers the
# round/bevel, not just the sharp pre-op edge line(s).
pad = float(feat.radius) if feat.radius else 0.0
cx, cy, cz = (xmin + xmax) / 2.0, (ymin + ymax) / 2.0, (zmin + zmax) / 2.0
w = max(2 * pad, (xmax - xmin) + 2 * pad)
h = max(2 * pad, (ymax - ymin) + 2 * pad)
d = max(2 * pad, (zmax - zmin) + 2 * pad)
box = BRepPrimAPI_MakeBox(
gp_Pnt(cx - w / 2.0, cy - h / 2.0, cz - d / 2.0), w, h, d
)
return box.Shape()
# ── Body highlight helpers ─────────────────────────────────────────
@@ -2777,10 +2864,9 @@ class MainWindow(QMainWindow):
"""Enable 'Del Op' / 'Mirror Op', highlight body or selected op in 3D."""
item = self._operations_list.currentItem()
# Restore the body we hid for the previous operation highlight.
if self._op_highlight_body_id is not None:
self._viewer_3d.set_visibility(self._op_highlight_body_id, True)
self._op_highlight_body_id = None
# Drop the previous highlight and any pending auto-clear so rapid
# re-selection always starts from a clean state.
self._op_highlight_timer.stop()
self._viewer_3d.clear_operation_highlight()
# Restore the previous body colour highlight.
@@ -2805,18 +2891,24 @@ class MainWindow(QMainWindow):
body, features, index, feat
)
if shape is not None:
if body.render_object is not None and body.visible:
self._viewer_3d.set_visibility(
body.render_object, False
)
self._op_highlight_body_id = body.render_object
# Overlay the operation geometry on the still-visible
# body as a hot-pink flash. The overlay is
# non-selectable, so face/edge/vertex picking keeps
# working while it shows.
self._viewer_3d.highlight_operation(shape)
# Auto-clear after 1 s so the view returns to normal
# and the user can work on the body.
self._op_highlight_timer.start(1000)
except Exception:
logger.debug("op highlight replay failed", exc_info=True)
else:
# Base operation (or non-selectable) — highlight body light blue.
self._highlight_selected_body_light_blue()
def _on_op_highlight_timeout(self) -> None:
"""1 s elapsed — drop the operation highlight, restoring the normal view."""
self._viewer_3d.clear_operation_highlight()
def _on_mirror_operation(self) -> None:
"""Mirror the body at the selected operation's point in the feature history.