- Operation highlighting, body highlighting
This commit is contained in:
Generated
+59
-47
@@ -6,13 +6,9 @@
|
|||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="8f0bafd6-58a0-4b20-aa2b-ddc3ba278873" name="Changes" comment="- arc improvements, fillets, operations, bodys">
|
<list default="true" id="8f0bafd6-58a0-4b20-aa2b-ddc3ba278873" name="Changes" comment="- arc improvements, fillets, operations, bodys">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/gui.ui" beforeDir="false" afterPath="$PROJECT_DIR$/gui.ui" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/fluency/rendering/occ_renderer.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/rendering/occ_renderer.py" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/gui_ui.py" beforeDir="false" afterPath="$PROJECT_DIR$/gui_ui.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/fluency/io/project_io.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/io/project_io.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/fluency/models/data_model.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/models/data_model.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/fluency/ui/dialogs.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/ui/dialogs.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/fluency/ui/main_window.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/ui/main_window.py" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/fluency/ui/main_window.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/ui/main_window.py" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/fluency/ui/sketch_widget.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/ui/sketch_widget.py" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/fluency/ui/viewer_widget.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/ui/viewer_widget.py" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -49,47 +45,47 @@
|
|||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
<option name="showLibraryContents" value="true" />
|
<option name="showLibraryContents" value="true" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PropertiesComponent"><![CDATA[{
|
<component name="PropertiesComponent">{
|
||||||
"keyToString": {
|
"keyToString": {
|
||||||
"Python.2dtest.executor": "Run",
|
"Python.2dtest.executor": "Run",
|
||||||
"Python.3d_windows.executor": "Run",
|
"Python.3d_windows.executor": "Run",
|
||||||
"Python.Unnamed.executor": "Run",
|
"Python.Unnamed.executor": "Run",
|
||||||
"Python.base.executor": "Run",
|
"Python.base.executor": "Run",
|
||||||
"Python.data_model.executor": "Run",
|
"Python.data_model.executor": "Run",
|
||||||
"Python.debug_dragging.executor": "Run",
|
"Python.debug_dragging.executor": "Run",
|
||||||
"Python.draw_widget2d.executor": "Run",
|
"Python.draw_widget2d.executor": "Run",
|
||||||
"Python.draw_widget_solve.executor": "Run",
|
"Python.draw_widget_solve.executor": "Run",
|
||||||
"Python.fluency.executor": "Run",
|
"Python.fluency.executor": "Run",
|
||||||
"Python.fluencyb.executor": "Run",
|
"Python.fluencyb.executor": "Run",
|
||||||
"Python.gl_widget.executor": "Run",
|
"Python.gl_widget.executor": "Run",
|
||||||
"Python.gui_ui.executor": "Run",
|
"Python.gui_ui.executor": "Run",
|
||||||
"Python.kernel.executor": "Run",
|
"Python.kernel.executor": "Run",
|
||||||
"Python.main.executor": "Run",
|
"Python.main.executor": "Run",
|
||||||
"Python.main_window.executor": "Run",
|
"Python.main_window.executor": "Run",
|
||||||
"Python.meshtest.executor": "Run",
|
"Python.meshtest.executor": "Run",
|
||||||
"Python.occ_renderer.executor": "Run",
|
"Python.occ_renderer.executor": "Run",
|
||||||
"Python.occ_to_mesh.executor": "Run",
|
"Python.occ_to_mesh.executor": "Run",
|
||||||
"Python.render_backend.executor": "Run",
|
"Python.render_backend.executor": "Run",
|
||||||
"Python.side_fluency.executor": "Run",
|
"Python.side_fluency.executor": "Run",
|
||||||
"Python.simple_mesh.executor": "Run",
|
"Python.simple_mesh.executor": "Run",
|
||||||
"Python.sketch.executor": "Run",
|
"Python.sketch.executor": "Run",
|
||||||
"Python.vtk_widget.executor": "Run",
|
"Python.vtk_widget.executor": "Run",
|
||||||
"Python.vulkan.executor": "Run",
|
"Python.vulkan.executor": "Run",
|
||||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||||
"codeWithMe.voiceChat.enabledByDefault": "false",
|
"codeWithMe.voiceChat.enabledByDefault": "false",
|
||||||
"git-widget-placeholder": "feature/occ-migration",
|
"git-widget-placeholder": "feature/occ-migration",
|
||||||
"last_opened_file_path": "/Volumes/Data_drive/Programming/fluency/src/fluency/Tesfiles",
|
"last_opened_file_path": "/Volumes/Data_drive/Programming/fluency/src/fluency/Tesfiles",
|
||||||
"node.js.detected.package.eslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
"nodejs_package_manager_path": "npm",
|
"nodejs_package_manager_path": "npm",
|
||||||
"settings.editor.selected.configurable": "project.propVCSSupport.DirectoryMappings"
|
"settings.editor.selected.configurable": "project.propVCSSupport.DirectoryMappings"
|
||||||
}
|
}
|
||||||
}]]></component>
|
}</component>
|
||||||
<component name="QodanaReportsService">
|
<component name="QodanaReportsService">
|
||||||
<option name="descriptions">
|
<option name="descriptions">
|
||||||
<ReportDescription localRun="true" path="/private/var/folders/kg/zm48w_r96yb68mlbzvb9gtq40000gn/T/qodana_output/qodana.sarif.json" reportGuid="5f5b823c-c594-48c5-ae1f-062e30303918" reportId="fluency/qodana/2024-02-04" />
|
<ReportDescription localRun="true" path="/private/var/folders/kg/zm48w_r96yb68mlbzvb9gtq40000gn/T/qodana_output/qodana.sarif.json" reportGuid="5f5b823c-c594-48c5-ae1f-062e30303918" reportId="fluency/qodana/2024-02-04" />
|
||||||
@@ -491,7 +487,23 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1785948828454</updated>
|
<updated>1785948828454</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="47" />
|
<task id="LOCAL-00047" summary="- arc improvements, fillets, operations, bodys">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1786110295218</created>
|
||||||
|
<option name="number" value="00047" />
|
||||||
|
<option name="presentableId" value="LOCAL-00047" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1786110295218</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00048" summary="- arc improvements, fillets, operations, bodys">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1786125317688</created>
|
||||||
|
<option name="number" value="00048" />
|
||||||
|
<option name="presentableId" value="LOCAL-00048" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1786125317688</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="49" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
|||||||
@@ -157,6 +157,8 @@ class OCCRenderer(Renderer):
|
|||||||
self._highlight_ais: Any = None
|
self._highlight_ais: Any = None
|
||||||
# Overlays for the fillet tool's two picked faces (one AIS per face).
|
# Overlays for the fillet tool's two picked faces (one AIS per face).
|
||||||
self._faces_highlight_ais: List[Any] = []
|
self._faces_highlight_ais: List[Any] = []
|
||||||
|
# Hot-pink translucent overlay for operation-history highlighting.
|
||||||
|
self._op_highlight_ais: Any = None
|
||||||
# Temporary transparent preview AIS for the live extrude/cut dialog.
|
# Temporary transparent preview AIS for the live extrude/cut dialog.
|
||||||
self._preview_ais: Any = None
|
self._preview_ais: Any = None
|
||||||
# Smart entity picker gizmo objects (snap markers, axis lines, rings).
|
# Smart entity picker gizmo objects (snap markers, axis lines, rings).
|
||||||
@@ -543,6 +545,16 @@ class OCCRenderer(Renderer):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def set_color(
|
||||||
|
self, obj_id: str, color: Tuple[float, float, float]
|
||||||
|
) -> bool:
|
||||||
|
"""Set the colour of an object by ID. Returns True on success."""
|
||||||
|
obj = self._objects.get(obj_id)
|
||||||
|
if obj is None:
|
||||||
|
return False
|
||||||
|
self.set_object_color(obj, color)
|
||||||
|
return True
|
||||||
|
|
||||||
# ─── Live preview (extrude/cut preview) ──────────────────────────────
|
# ─── Live preview (extrude/cut preview) ──────────────────────────────
|
||||||
|
|
||||||
_PREVIEW_ID = "__extrude_preview__"
|
_PREVIEW_ID = "__extrude_preview__"
|
||||||
@@ -608,6 +620,7 @@ class OCCRenderer(Renderer):
|
|||||||
return
|
return
|
||||||
self.clear_preview()
|
self.clear_preview()
|
||||||
self.clear_face_highlight()
|
self.clear_face_highlight()
|
||||||
|
self.clear_operation_highlight()
|
||||||
self.clear_entity_gizmo()
|
self.clear_entity_gizmo()
|
||||||
# The sketch reference gizmo is scene-anchored — drop it with the rest.
|
# The sketch reference gizmo is scene-anchored — drop it with the rest.
|
||||||
self.remove_sketch_gizmo()
|
self.remove_sketch_gizmo()
|
||||||
@@ -1385,6 +1398,56 @@ class OCCRenderer(Renderer):
|
|||||||
logger.debug("clear_faces_highlight remove failed", exc_info=True)
|
logger.debug("clear_faces_highlight remove failed", exc_info=True)
|
||||||
self._faces_highlight_ais = []
|
self._faces_highlight_ais = []
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Operation history highlight ─────────────────────────────────────
|
||||||
|
|
||||||
|
def highlight_operation_shape(
|
||||||
|
self, shape: Any, color: Tuple[float, float, float] = (1.0, 0.08, 0.58)
|
||||||
|
) -> None:
|
||||||
|
"""Overlay a translucent hot-pink *shape* on the 3D view.
|
||||||
|
|
||||||
|
Used to show the intermediate geometry at a selected operation in
|
||||||
|
the feature-history list. The overlay is an independent
|
||||||
|
``AIS_Shape`` with polygon offset so it draws on top of the
|
||||||
|
coincident body surface without z-fighting. Replaces any previous
|
||||||
|
operation highlight.
|
||||||
|
"""
|
||||||
|
if self._context is None:
|
||||||
|
return
|
||||||
|
self.clear_operation_highlight()
|
||||||
|
from OCP.AIS import AIS_Shape
|
||||||
|
from OCP.Quantity import Quantity_Color, Quantity_TOC_RGB
|
||||||
|
|
||||||
|
ais = AIS_Shape(shape)
|
||||||
|
try:
|
||||||
|
ais.SetMaterial(self._default_material())
|
||||||
|
except Exception:
|
||||||
|
logger.debug("op highlight material set failed", exc_info=True)
|
||||||
|
ais.SetColor(Quantity_Color(*color, Quantity_TOC_RGB))
|
||||||
|
ais.SetDisplayMode(1) # shaded
|
||||||
|
try:
|
||||||
|
ais.SetTransparency(0.65)
|
||||||
|
except Exception:
|
||||||
|
logger.debug("op highlight transparency 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._op_highlight_ais = ais
|
||||||
|
if self._view is not None:
|
||||||
|
self._view.Redraw()
|
||||||
|
|
||||||
|
def clear_operation_highlight(self) -> None:
|
||||||
|
"""Remove the operation-history overlay, if any."""
|
||||||
|
if self._context is None or self._op_highlight_ais is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self._context.Remove(self._op_highlight_ais, True)
|
||||||
|
except Exception:
|
||||||
|
logger.debug("clear_operation_highlight remove failed", exc_info=True)
|
||||||
|
self._op_highlight_ais = None
|
||||||
|
|
||||||
# ─── General entity picking (for assembly connectors / snaps) ───────────
|
# ─── General entity picking (for assembly connectors / snaps) ───────────
|
||||||
|
|
||||||
def pick_entity(self, x: int, y: int) -> Optional[Dict[str, Any]]:
|
def pick_entity(self, x: int, y: int) -> Optional[Dict[str, Any]]:
|
||||||
|
|||||||
@@ -1423,6 +1423,13 @@ class MainWindow(QMainWindow):
|
|||||||
# selectable axis / centre / plane from the 3D viewport.
|
# selectable axis / centre / plane from the 3D viewport.
|
||||||
self._sketch_gizmo_selection: Optional[dict] = None
|
self._sketch_gizmo_selection: Optional[dict] = None
|
||||||
self._selected_body: Optional[Body] = 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
|
||||||
|
# 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
|
||||||
|
self._body_highlight_original_color: Optional[Tuple[float, float, float]] = None
|
||||||
|
|
||||||
# Fillet tool: two-face pick flow (face 1 → face 2 → options dialog).
|
# Fillet tool: two-face pick flow (face 1 → face 2 → options dialog).
|
||||||
self._fillet_pick_active: bool = False
|
self._fillet_pick_active: bool = False
|
||||||
@@ -2345,19 +2352,154 @@ class MainWindow(QMainWindow):
|
|||||||
item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsSelectable)
|
item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsSelectable)
|
||||||
self._operations_list.addItem(item)
|
self._operations_list.addItem(item)
|
||||||
self._operations_list.setCurrentRow(min(1, len(features) - 1))
|
self._operations_list.setCurrentRow(min(1, len(features) - 1))
|
||||||
self._on_operations_selection_changed()
|
# setCurrentRow doesn't fire for non-selectable items (the base);
|
||||||
|
# ensure buttons reflect the empty selection in that edge case.
|
||||||
|
if self._operations_list.currentItem() is None:
|
||||||
|
self._on_operations_selection_changed()
|
||||||
|
|
||||||
|
def _compute_operation_highlight_shape(
|
||||||
|
self, body: Body, features: List[Feature], index: int, feat: Feature
|
||||||
|
) -> Optional[Any]:
|
||||||
|
"""Return the shape to highlight for *feat* at *index* in the history.
|
||||||
|
|
||||||
|
For subtractive operations (cut), returns the tool shape — the
|
||||||
|
volume that was removed — so the user sees what was cut away.
|
||||||
|
For additive operations (union), returns the tool shape that was
|
||||||
|
added. For everything else, returns the intermediate body shape
|
||||||
|
after replaying features up to and including *index*.
|
||||||
|
"""
|
||||||
|
if feat.operation in ("cut", "union"):
|
||||||
|
return self._compute_tool_shape(body, features, index, feat)
|
||||||
|
# Default: show the intermediate body after this operation.
|
||||||
|
geom = _replay_body_features(
|
||||||
|
self._kernel, body, features[: index + 1],
|
||||||
|
self._through_all_length_for_geometry,
|
||||||
|
)
|
||||||
|
if geom is None:
|
||||||
|
return None
|
||||||
|
return self._kernel._get_shape(geom)
|
||||||
|
|
||||||
|
def _compute_tool_shape(
|
||||||
|
self, body: Body, features: List[Feature], index: int, feat: Feature
|
||||||
|
) -> Optional[Any]:
|
||||||
|
"""Compute the extruded tool shape for a cut or union feature.
|
||||||
|
|
||||||
|
Replays the body up to (but not including) *index* so the
|
||||||
|
pre-operation geometry is available for through-all length
|
||||||
|
calculation, then extrudes the feature's sketch profile to
|
||||||
|
produce the tool volume.
|
||||||
|
"""
|
||||||
|
sketch = feat.sketch
|
||||||
|
if sketch is None or sketch.occ_sketch is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
sketch.apply_workplane()
|
||||||
|
sketch.solve()
|
||||||
|
|
||||||
|
face_geom = _feature_face_geometry(body, feat, sketch.occ_sketch)
|
||||||
|
if face_geom is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# For through-all cuts we need the pre-op body to size the tool.
|
||||||
|
if feat.through_all:
|
||||||
|
pre_geom = _replay_body_features(
|
||||||
|
self._kernel, body, features[:index],
|
||||||
|
self._through_all_length_for_geometry,
|
||||||
|
)
|
||||||
|
if pre_geom is not None:
|
||||||
|
length = self._through_all_length_for_geometry(pre_geom, sketch)
|
||||||
|
symmetric = True
|
||||||
|
invert = False
|
||||||
|
else:
|
||||||
|
length = feat.length if feat.length is not None else 10.0
|
||||||
|
symmetric = feat.symmetric
|
||||||
|
invert = True # cut: go inward
|
||||||
|
elif feat.operation == "cut":
|
||||||
|
# Cut tool must go INTO the solid — the face normal points away,
|
||||||
|
# so force the extrude direction inward.
|
||||||
|
length = feat.length if feat.length is not None else 10.0
|
||||||
|
symmetric = feat.symmetric
|
||||||
|
invert = True
|
||||||
|
else:
|
||||||
|
length = feat.length if feat.length is not None else 10.0
|
||||||
|
symmetric = feat.symmetric
|
||||||
|
invert = feat.invert
|
||||||
|
|
||||||
|
tool_geom = self._kernel.extrude(
|
||||||
|
face_geom, -length if invert else length, symmetric=symmetric
|
||||||
|
)
|
||||||
|
if tool_geom is None:
|
||||||
|
return None
|
||||||
|
return self._kernel._get_shape(tool_geom)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Body highlight helpers ─────────────────────────────────────────
|
||||||
|
|
||||||
|
def _restore_body_highlight_color(self) -> None:
|
||||||
|
"""Restore the original colour of a previously highlighted body."""
|
||||||
|
if self._body_highlight_id is not None:
|
||||||
|
if self._body_highlight_original_color is not None:
|
||||||
|
self._viewer_3d.set_body_color(
|
||||||
|
self._body_highlight_id, self._body_highlight_original_color
|
||||||
|
)
|
||||||
|
self._body_highlight_id = None
|
||||||
|
self._body_highlight_original_color = None
|
||||||
|
|
||||||
|
def _highlight_selected_body_light_blue(self) -> None:
|
||||||
|
"""Tint the currently selected body light blue in the 3D view."""
|
||||||
|
body = self._selected_body
|
||||||
|
if body is None or body.render_object is None or not body.visible:
|
||||||
|
return
|
||||||
|
# Save the original colour so we can restore it later.
|
||||||
|
self._body_highlight_id = body.render_object
|
||||||
|
self._body_highlight_original_color = body.color
|
||||||
|
self._viewer_3d.set_body_color(
|
||||||
|
body.render_object, (0.45, 0.75, 1.0)
|
||||||
|
)
|
||||||
|
|
||||||
def _on_operations_selection_changed(self) -> None:
|
def _on_operations_selection_changed(self) -> None:
|
||||||
"""Enable 'Del Op' / 'Mirror Op' when a non-base op is selected."""
|
"""Enable 'Del Op' / 'Mirror Op', highlight body or selected op in 3D."""
|
||||||
item = self._operations_list.currentItem()
|
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
|
||||||
|
self._viewer_3d.clear_operation_highlight()
|
||||||
|
|
||||||
|
# Restore the previous body colour highlight.
|
||||||
|
self._restore_body_highlight_color()
|
||||||
|
|
||||||
if item is None:
|
if item is None:
|
||||||
self._btn_del_op.setEnabled(False)
|
self._btn_del_op.setEnabled(False)
|
||||||
self._btn_mirror_op.setEnabled(False)
|
self._btn_mirror_op.setEnabled(False)
|
||||||
|
self._highlight_selected_body_light_blue()
|
||||||
return
|
return
|
||||||
index = item.data(Qt.ItemDataRole.UserRole)
|
index = item.data(Qt.ItemDataRole.UserRole)
|
||||||
deletable = index is not None and index > 0
|
deletable = index is not None and index > 0
|
||||||
self._btn_del_op.setEnabled(deletable)
|
self._btn_del_op.setEnabled(deletable)
|
||||||
self._btn_mirror_op.setEnabled(index is not None and index >= 0)
|
self._btn_mirror_op.setEnabled(index is not None and index >= 0)
|
||||||
|
if index is not None and index > 0 and self._selected_body is not None:
|
||||||
|
body = self._selected_body
|
||||||
|
features = _ensure_feature_history(body)
|
||||||
|
if 0 <= index < len(features):
|
||||||
|
feat = features[index]
|
||||||
|
try:
|
||||||
|
shape = self._compute_operation_highlight_shape(
|
||||||
|
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
|
||||||
|
self._viewer_3d.highlight_operation(shape)
|
||||||
|
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_mirror_operation(self) -> None:
|
def _on_mirror_operation(self) -> None:
|
||||||
"""Mirror the body at the selected operation's point in the feature history.
|
"""Mirror the body at the selected operation's point in the feature history.
|
||||||
@@ -6747,6 +6889,9 @@ class MainWindow(QMainWindow):
|
|||||||
self._project = Project()
|
self._project = Project()
|
||||||
self._current_component = None
|
self._current_component = None
|
||||||
self._current_sketch = None
|
self._current_sketch = None
|
||||||
|
self._op_highlight_body_id = None
|
||||||
|
self._body_highlight_id = None
|
||||||
|
self._body_highlight_original_color = None
|
||||||
self._selected_body = None
|
self._selected_body = None
|
||||||
self._selected_assembly_component_id = None
|
self._selected_assembly_component_id = None
|
||||||
self._assembly_view_active = False
|
self._assembly_view_active = False
|
||||||
@@ -6994,6 +7139,9 @@ class MainWindow(QMainWindow):
|
|||||||
self._current_sketch = None
|
self._current_sketch = None
|
||||||
self._selected_body = None
|
self._selected_body = None
|
||||||
self._selected_assembly_component_id = None
|
self._selected_assembly_component_id = None
|
||||||
|
self._op_highlight_body_id = None
|
||||||
|
self._body_highlight_id = None
|
||||||
|
self._body_highlight_original_color = None
|
||||||
self._assembly_view_active = False
|
self._assembly_view_active = False
|
||||||
self._sketch_widget.clear_source_face()
|
self._sketch_widget.clear_source_face()
|
||||||
self._sketch_widget.set_sketch(None)
|
self._sketch_widget.set_sketch(None)
|
||||||
|
|||||||
@@ -279,6 +279,19 @@ class Viewer3DWidget(QWidget):
|
|||||||
self._renderer.render()
|
self._renderer.render()
|
||||||
return ok
|
return ok
|
||||||
|
|
||||||
|
def set_body_color(
|
||||||
|
self, mesh_id: str, color: Tuple[float, float, float]
|
||||||
|
) -> bool:
|
||||||
|
"""Change the colour of a body in the 3D view. Returns True on success."""
|
||||||
|
self._ensure_initialized()
|
||||||
|
fn = getattr(self._renderer, "set_color", None)
|
||||||
|
if fn is None:
|
||||||
|
return False
|
||||||
|
ok = fn(mesh_id, color)
|
||||||
|
if ok:
|
||||||
|
self._renderer.render()
|
||||||
|
return ok
|
||||||
|
|
||||||
def set_transparency(self, mesh_id: str, transparency: float) -> bool:
|
def set_transparency(self, mesh_id: str, transparency: float) -> bool:
|
||||||
"""Set a previously-added mesh's transparency (0..1).
|
"""Set a previously-added mesh's transparency (0..1).
|
||||||
|
|
||||||
@@ -862,6 +875,22 @@ class Viewer3DWidget(QWidget):
|
|||||||
fn()
|
fn()
|
||||||
self._renderer.render()
|
self._renderer.render()
|
||||||
|
|
||||||
|
def highlight_operation(self, shape: Any) -> None:
|
||||||
|
"""Overlay a hot-pink translucent *shape* to show operation history."""
|
||||||
|
self._ensure_initialized()
|
||||||
|
fn = getattr(self._renderer, "highlight_operation_shape", None)
|
||||||
|
if fn is not None:
|
||||||
|
fn(shape)
|
||||||
|
self._renderer.render()
|
||||||
|
|
||||||
|
def clear_operation_highlight(self) -> None:
|
||||||
|
"""Remove the operation-history overlay, if any."""
|
||||||
|
self._ensure_initialized()
|
||||||
|
fn = getattr(self._renderer, "clear_operation_highlight", None)
|
||||||
|
if fn is not None:
|
||||||
|
fn()
|
||||||
|
self._renderer.render()
|
||||||
|
|
||||||
# ─── Connector pick mode (assembly) ────────────────────────────────────
|
# ─── Connector pick mode (assembly) ────────────────────────────────────
|
||||||
|
|
||||||
def set_connector_pick_mode(self, enabled: bool, clear_gizmo: bool = True) -> None:
|
def set_connector_pick_mode(self, enabled: bool, clear_gizmo: bool = True) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user