feat: 完善特征编辑和选择稳定性
This commit is contained in:
+261
-23
@@ -43,15 +43,35 @@ class WindowCoreMixin:
|
||||
if watched is getattr(self, "vtk_widget", None):
|
||||
event_type = event.type()
|
||||
if event_type == QEvent.Type.MouseButtonRelease:
|
||||
if event.button() == Qt.MouseButton.LeftButton:
|
||||
x, y = self._vtk_position_from_qt_event(event)
|
||||
self.skip_next_vtk_left_release = True
|
||||
self._handle_left_button_release(x, y)
|
||||
self.pointer_button_down = bool(event.buttons() != Qt.MouseButton.NoButton)
|
||||
elif event_type == QEvent.Type.MouseButtonPress:
|
||||
if event.button() == Qt.MouseButton.LeftButton:
|
||||
x, y = self._vtk_position_from_qt_event(event)
|
||||
self.skip_next_vtk_left_press = True
|
||||
self._handle_left_button_press(x, y)
|
||||
self.pointer_button_down = True
|
||||
elif event_type == QEvent.Type.MouseMove:
|
||||
self.pointer_button_down = bool(event.buttons() != Qt.MouseButton.NoButton)
|
||||
if event.buttons() & Qt.MouseButton.LeftButton:
|
||||
x, y = self._vtk_position_from_qt_event(event)
|
||||
self._update_left_button_drag_state(x, y)
|
||||
if not self.pointer_button_down:
|
||||
self._queue_hover_from_qt_event(event)
|
||||
elif event_type == QEvent.Type.Leave:
|
||||
self._cancel_hover_tracking(render=True)
|
||||
return super().eventFilter(watched, event)
|
||||
|
||||
def _vtk_position_from_qt_event(self, event) -> tuple[int, int]:
|
||||
position = event.position() if hasattr(event, "position") else event.pos()
|
||||
scale = self.vtk_widget._getPixelRatio() if hasattr(self.vtk_widget, "_getPixelRatio") else 1.0
|
||||
x = int(round(float(position.x()) * scale))
|
||||
y = int(round((float(self.vtk_widget.height()) - float(position.y()) - 1.0) * scale))
|
||||
return x, y
|
||||
|
||||
def _build_vtk(self) -> None:
|
||||
self.renderer = vtk.vtkRenderer()
|
||||
self.renderer.SetBackground(*VIEW_BACKGROUND_COLOR)
|
||||
@@ -62,13 +82,19 @@ class WindowCoreMixin:
|
||||
self.interactor.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera())
|
||||
self.picker = vtk.vtkCellPicker()
|
||||
self.picker.SetTolerance(0.003)
|
||||
self.interactor.AddObserver("LeftButtonPressEvent", self.on_left_click)
|
||||
self.interactor.AddObserver("LeftButtonReleaseEvent", self.on_pointer_button_release)
|
||||
self.interactor.AddObserver("LeftButtonPressEvent", self.on_left_button_press)
|
||||
self.interactor.AddObserver("LeftButtonReleaseEvent", self.on_left_button_release)
|
||||
self.interactor.AddObserver("MiddleButtonPressEvent", self.on_pointer_button_press)
|
||||
self.interactor.AddObserver("MiddleButtonReleaseEvent", self.on_pointer_button_release)
|
||||
self.interactor.AddObserver("RightButtonPressEvent", self.on_pointer_button_press)
|
||||
self.interactor.AddObserver("RightButtonReleaseEvent", self.on_pointer_button_release)
|
||||
self.interactor.AddObserver("MouseMoveEvent", self.on_mouse_move)
|
||||
self.interactor.AddObserver("StartInteractionEvent", self.on_camera_interaction_start)
|
||||
self.interactor.AddObserver("EndInteractionEvent", self.on_camera_interaction_end)
|
||||
if hasattr(self.interactor, "SetDesiredUpdateRate"):
|
||||
self.interactor.SetDesiredUpdateRate(24.0)
|
||||
if hasattr(self.interactor, "SetStillUpdateRate"):
|
||||
self.interactor.SetStillUpdateRate(0.2)
|
||||
|
||||
light = vtk.vtkLight()
|
||||
light.SetLightTypeToHeadlight()
|
||||
@@ -78,6 +104,55 @@ class WindowCoreMixin:
|
||||
|
||||
self.interactor.Initialize()
|
||||
|
||||
def on_camera_interaction_start(self, _obj, _event) -> None:
|
||||
if not self._is_ui_thread():
|
||||
self._invoke_on_ui_thread(self._begin_camera_interaction)
|
||||
return
|
||||
self._begin_camera_interaction()
|
||||
|
||||
def on_camera_interaction_end(self, _obj, _event) -> None:
|
||||
if not self._is_ui_thread():
|
||||
self._invoke_on_ui_thread(self._end_camera_interaction)
|
||||
return
|
||||
self._end_camera_interaction()
|
||||
|
||||
def _begin_camera_interaction(self) -> None:
|
||||
if getattr(self, "camera_interaction_active", False):
|
||||
return
|
||||
self.camera_interaction_active = True
|
||||
self._cancel_hover_tracking(render=False)
|
||||
if self.edge_actor is not None:
|
||||
try:
|
||||
self.edge_actor_visible_before_interaction = bool(self.edge_actor.GetVisibility())
|
||||
self.edge_actor.SetVisibility(False)
|
||||
except Exception:
|
||||
self.edge_actor_visible_before_interaction = True
|
||||
if self.model_actor is not None:
|
||||
try:
|
||||
prop = self.model_actor.GetProperty()
|
||||
self.model_interpolation_before_interaction = int(prop.GetInterpolation())
|
||||
prop.SetInterpolationToGouraud()
|
||||
except Exception:
|
||||
self.model_interpolation_before_interaction = None
|
||||
|
||||
def _end_camera_interaction(self) -> None:
|
||||
if not getattr(self, "camera_interaction_active", False):
|
||||
return
|
||||
self.camera_interaction_active = False
|
||||
if self.edge_actor is not None:
|
||||
try:
|
||||
self.edge_actor.SetVisibility(bool(getattr(self, "edge_actor_visible_before_interaction", True)))
|
||||
except Exception:
|
||||
pass
|
||||
if self.model_actor is not None and self.model_interpolation_before_interaction is not None:
|
||||
try:
|
||||
self.model_actor.GetProperty().SetInterpolation(int(self.model_interpolation_before_interaction))
|
||||
except Exception:
|
||||
pass
|
||||
self.model_interpolation_before_interaction = None
|
||||
if hasattr(self, "render_window"):
|
||||
self.render_window.Render()
|
||||
|
||||
def closeEvent(self, event) -> None:
|
||||
load_thread_running = bool(
|
||||
(self.load_thread is not None and self.load_thread.isRunning())
|
||||
@@ -839,18 +914,73 @@ class WindowCoreMixin:
|
||||
if hasattr(self, "_update_id_select_title"):
|
||||
self._update_id_select_title(mode)
|
||||
|
||||
def on_left_click(self, _obj, _event) -> None:
|
||||
def _cancel_hover_tracking(self, render: bool = True) -> None:
|
||||
self.pending_hover_position = None
|
||||
self.last_hover_pick_position = None
|
||||
if hasattr(self, "hover_timer"):
|
||||
self.hover_timer.stop()
|
||||
self._clear_hover(render=render)
|
||||
|
||||
def on_left_button_press(self, _obj, _event) -> None:
|
||||
if getattr(self, "skip_next_vtk_left_press", False):
|
||||
self.skip_next_vtk_left_press = False
|
||||
return
|
||||
try:
|
||||
x, y = self.interactor.GetEventPosition()
|
||||
except Exception:
|
||||
x, y = 0, 0
|
||||
if not self._is_ui_thread():
|
||||
self._invoke_on_ui_thread(lambda x=int(x), y=int(y): self._handle_left_click(x, y))
|
||||
self._invoke_on_ui_thread(lambda x=int(x), y=int(y): self._handle_left_button_press(x, y))
|
||||
return
|
||||
self._handle_left_click(int(x), int(y))
|
||||
self._handle_left_button_press(int(x), int(y))
|
||||
|
||||
def _handle_left_button_press(self, x: int, y: int) -> None:
|
||||
self.pointer_button_down = True
|
||||
self.left_button_press_position = (int(x), int(y))
|
||||
self.left_button_dragged = False
|
||||
self.pending_hover_position = None
|
||||
self.last_hover_pick_position = None
|
||||
self.hover_timer.stop()
|
||||
self._clear_hover(render=True)
|
||||
|
||||
def on_left_button_release(self, _obj, _event) -> None:
|
||||
if getattr(self, "skip_next_vtk_left_release", False):
|
||||
self.skip_next_vtk_left_release = False
|
||||
return
|
||||
try:
|
||||
x, y = self.interactor.GetEventPosition()
|
||||
except Exception:
|
||||
x, y = 0, 0
|
||||
if not self._is_ui_thread():
|
||||
self._invoke_on_ui_thread(lambda x=int(x), y=int(y): self._handle_left_button_release(x, y))
|
||||
return
|
||||
self._handle_left_button_release(int(x), int(y))
|
||||
|
||||
def _handle_left_button_release(self, x: int, y: int) -> None:
|
||||
self._update_left_button_drag_state(x, y)
|
||||
was_dragged = bool(getattr(self, "left_button_dragged", False))
|
||||
self.pointer_button_down = False
|
||||
self.left_button_press_position = None
|
||||
self.left_button_dragged = False
|
||||
self.pending_hover_position = None
|
||||
self.last_hover_pick_position = None
|
||||
if getattr(self, "camera_interaction_active", False):
|
||||
self._end_camera_interaction()
|
||||
if was_dragged:
|
||||
return
|
||||
self._handle_left_click(x, y)
|
||||
|
||||
def _update_left_button_drag_state(self, x: int, y: int) -> None:
|
||||
press_position = getattr(self, "left_button_press_position", None)
|
||||
if press_position is None:
|
||||
return
|
||||
threshold = int(getattr(self, "left_click_drag_threshold_px", 6) or 6)
|
||||
dx = abs(int(x) - int(press_position[0]))
|
||||
dy = abs(int(y) - int(press_position[1]))
|
||||
if max(dx, dy) > threshold:
|
||||
self.left_button_dragged = True
|
||||
|
||||
def _handle_left_click(self, x: int, y: int) -> None:
|
||||
self.pointer_button_down = True
|
||||
self.pending_hover_position = None
|
||||
self.last_hover_pick_position = None
|
||||
self.hover_timer.stop()
|
||||
@@ -868,7 +998,11 @@ class WindowCoreMixin:
|
||||
mode = self._current_selection_mode()
|
||||
target = self._pick_selection_target(mode, x, y)
|
||||
if target is None:
|
||||
self.statusBar().showMessage("未选中对象")
|
||||
self._cancel_hover_tracking(render=False)
|
||||
self._reset_selection(clear_highlight=True)
|
||||
if hasattr(self, "render_window"):
|
||||
self.render_window.Render()
|
||||
self.statusBar().showMessage("已取消选择")
|
||||
return
|
||||
|
||||
self._clear_hover(render=False)
|
||||
@@ -897,6 +1031,7 @@ class WindowCoreMixin:
|
||||
self.pointer_button_down = False
|
||||
self.pending_hover_position = None
|
||||
self.last_hover_pick_position = None
|
||||
self._end_camera_interaction()
|
||||
|
||||
def on_mouse_move(self, _obj, _event) -> None:
|
||||
try:
|
||||
@@ -909,8 +1044,15 @@ class WindowCoreMixin:
|
||||
self._handle_mouse_move(int(x), int(y))
|
||||
|
||||
def _handle_mouse_move(self, x: int, y: int) -> None:
|
||||
if QApplication.mouseButtons() != Qt.MouseButton.NoButton:
|
||||
buttons = QApplication.mouseButtons()
|
||||
if buttons != Qt.MouseButton.NoButton:
|
||||
self.pointer_button_down = True
|
||||
if buttons & Qt.MouseButton.LeftButton:
|
||||
self._update_left_button_drag_state(x, y)
|
||||
if getattr(self, "left_button_dragged", False):
|
||||
self._begin_camera_interaction()
|
||||
else:
|
||||
self._begin_camera_interaction()
|
||||
return
|
||||
self.pointer_button_down = False
|
||||
if (
|
||||
@@ -934,10 +1076,7 @@ class WindowCoreMixin:
|
||||
):
|
||||
self._clear_hover(render=True)
|
||||
return
|
||||
position = event.position() if hasattr(event, "position") else event.pos()
|
||||
scale = self.vtk_widget._getPixelRatio() if hasattr(self.vtk_widget, "_getPixelRatio") else 1.0
|
||||
x = int(round(float(position.x()) * scale))
|
||||
y = int(round((float(self.vtk_widget.height()) - float(position.y()) - 1.0) * scale))
|
||||
x, y = self._vtk_position_from_qt_event(event)
|
||||
self._queue_hover_position(x, y)
|
||||
|
||||
def _queue_hover_position(self, x: int, y: int) -> None:
|
||||
@@ -1213,10 +1352,26 @@ class WindowCoreMixin:
|
||||
self._set_selection_mode("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可调整凸台直径候选Face {target_id}")
|
||||
elif action == "resize_boss_height":
|
||||
self._set_selection_mode("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可调整凸台高度候选Face {target_id}")
|
||||
elif action == "move_boss_axis":
|
||||
self._set_selection_mode("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可移动凸台轴心候选Face {target_id}")
|
||||
elif action == "resize_slot_width":
|
||||
self._set_selection_mode("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可调整槽/半孔宽度候选Face {target_id}")
|
||||
elif action == "resize_slot_depth":
|
||||
self._set_selection_mode("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可调整槽/半孔深度候选Face {target_id}")
|
||||
elif action == "resize_slot_arc_length":
|
||||
self._set_selection_mode("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可调整槽/半孔圆弧长度候选Face {target_id}")
|
||||
elif action == "resize_shell_thickness":
|
||||
self._set_selection_mode("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
@@ -1228,7 +1383,7 @@ class WindowCoreMixin:
|
||||
elif action == "resize_depth":
|
||||
self._set_selection_mode("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
self.statusBar().showMessage(f"已选择可调整盲孔深度候选Face {target_id}")
|
||||
self.statusBar().showMessage(f"已选择可调整盲孔/盲槽深度候选Face {target_id}")
|
||||
elif action == "inspect_existing_fillet":
|
||||
self._set_selection_mode("Feature")
|
||||
self.select_feature(int(target_id))
|
||||
@@ -1305,7 +1460,7 @@ class WindowCoreMixin:
|
||||
if self._edit_busy("编辑计算中,暂时不能选择零件。"):
|
||||
return
|
||||
info = self.model.part_info(part_id)
|
||||
self._reset_selection(clear_highlight=False)
|
||||
self._reset_selection(clear_highlight=False, clear_info=False)
|
||||
self.selected_kind = "part"
|
||||
self.selected_part_id = part_id
|
||||
self.selected_pick_position = pick_position
|
||||
@@ -1328,7 +1483,7 @@ class WindowCoreMixin:
|
||||
if self._edit_busy("编辑计算中,暂时不能选择Solid。"):
|
||||
return
|
||||
info = self.model.solid_info(solid_id)
|
||||
self._reset_selection(clear_highlight=False)
|
||||
self._reset_selection(clear_highlight=False, clear_info=False)
|
||||
self.selected_kind = "solid"
|
||||
self.selected_part_id = part_id
|
||||
self.selected_solid_id = solid_id
|
||||
@@ -1351,7 +1506,7 @@ class WindowCoreMixin:
|
||||
return
|
||||
if self._edit_busy("编辑计算中,暂时不能选择Face。"):
|
||||
return
|
||||
self._reset_selection(clear_highlight=False)
|
||||
self._reset_selection(clear_highlight=False, clear_info=False)
|
||||
self.selected_kind = "feature" if feature_mode else "face"
|
||||
self.selected_face_id = face_id
|
||||
self.selected_pick_position = pick_position
|
||||
@@ -1386,7 +1541,7 @@ class WindowCoreMixin:
|
||||
if self._edit_busy("编辑计算中,暂时不能选择特征。"):
|
||||
return
|
||||
info = self.model.feature_info(face_id)
|
||||
self._reset_selection(clear_highlight=False)
|
||||
self._reset_selection(clear_highlight=False, clear_info=False)
|
||||
self.selected_kind = "feature"
|
||||
self.selected_face_id = face_id
|
||||
self.selected_pick_position = pick_position
|
||||
@@ -1417,7 +1572,21 @@ class WindowCoreMixin:
|
||||
self.hole_diameter_input.clear()
|
||||
if hasattr(self, "slot_width_input"):
|
||||
self.slot_width_input.clear()
|
||||
if hasattr(self, "slot_depth_input"):
|
||||
self.slot_depth_input.clear()
|
||||
if hasattr(self, "slot_arc_length_input"):
|
||||
self.slot_arc_length_input.clear()
|
||||
if hasattr(self, "slot_total_length_input"):
|
||||
self.slot_total_length_input.clear()
|
||||
self.boss_diameter_input.setText(suggested_diameter)
|
||||
if hasattr(self, "boss_height_input"):
|
||||
boss_height = _float_or_none(info.get("same_domain_height_estimate"))
|
||||
if boss_height is None:
|
||||
boss_height = _float_or_none(info.get("height_estimate"))
|
||||
if boss_height is not None and boss_height > 0:
|
||||
self.boss_height_input.setText(_format_float(boss_height * 1.2))
|
||||
else:
|
||||
self.boss_height_input.clear()
|
||||
elif feature_guess == "round/fillet candidate":
|
||||
radius = _float_or_none(info.get("existing_fillet_radius_estimate"))
|
||||
if radius is None:
|
||||
@@ -1427,22 +1596,54 @@ class WindowCoreMixin:
|
||||
self.hole_diameter_input.clear()
|
||||
if hasattr(self, "slot_width_input"):
|
||||
self.slot_width_input.clear()
|
||||
if hasattr(self, "slot_depth_input"):
|
||||
self.slot_depth_input.clear()
|
||||
if hasattr(self, "slot_arc_length_input"):
|
||||
self.slot_arc_length_input.clear()
|
||||
if hasattr(self, "slot_total_length_input"):
|
||||
self.slot_total_length_input.clear()
|
||||
self.boss_diameter_input.clear()
|
||||
if hasattr(self, "boss_height_input"):
|
||||
self.boss_height_input.clear()
|
||||
else:
|
||||
suggested_diameter = _format_float(float(info["diameter"]) * 1.2)
|
||||
self.hole_diameter_input.setText(suggested_diameter)
|
||||
self.boss_diameter_input.clear()
|
||||
if hasattr(self, "boss_height_input"):
|
||||
self.boss_height_input.clear()
|
||||
if hasattr(self, "slot_width_input"):
|
||||
slot_width = _float_or_none(info.get("slot_chord_width_estimate"))
|
||||
if slot_width is not None and slot_width > 0:
|
||||
self.slot_width_input.setText(_format_float(slot_width * 1.2))
|
||||
else:
|
||||
self.slot_width_input.clear()
|
||||
if hasattr(self, "slot_depth_input"):
|
||||
slot_depth = _float_or_none(info.get("slot_sagitta_depth_estimate"))
|
||||
if slot_depth is not None and slot_depth > 0:
|
||||
self.slot_depth_input.setText(_format_float(slot_depth * 1.2))
|
||||
else:
|
||||
self.slot_depth_input.clear()
|
||||
if hasattr(self, "slot_arc_length_input"):
|
||||
slot_arc = _float_or_none(info.get("slot_arc_length_estimate"))
|
||||
if slot_arc is not None and slot_arc > 0:
|
||||
self.slot_arc_length_input.setText(_format_float(slot_arc * 1.2))
|
||||
else:
|
||||
self.slot_arc_length_input.clear()
|
||||
if hasattr(self, "slot_total_length_input"):
|
||||
self.slot_total_length_input.clear()
|
||||
else:
|
||||
self.hole_diameter_input.clear()
|
||||
if hasattr(self, "slot_width_input"):
|
||||
self.slot_width_input.clear()
|
||||
if hasattr(self, "slot_depth_input"):
|
||||
self.slot_depth_input.clear()
|
||||
if hasattr(self, "slot_arc_length_input"):
|
||||
self.slot_arc_length_input.clear()
|
||||
if hasattr(self, "slot_total_length_input"):
|
||||
self.slot_total_length_input.clear()
|
||||
self.boss_diameter_input.clear()
|
||||
if hasattr(self, "boss_height_input"):
|
||||
self.boss_height_input.clear()
|
||||
if info.get("cylinder_end_type") == "blind" and "hole_depth_estimate" in info:
|
||||
self.hole_depth_input.setText(_format_float(float(info["hole_depth_estimate"]) * 1.2))
|
||||
else:
|
||||
@@ -1469,7 +1670,15 @@ class WindowCoreMixin:
|
||||
self.shell_thickness_input.clear()
|
||||
if hasattr(self, "slot_width_input"):
|
||||
self.slot_width_input.clear()
|
||||
if hasattr(self, "slot_depth_input"):
|
||||
self.slot_depth_input.clear()
|
||||
if hasattr(self, "slot_arc_length_input"):
|
||||
self.slot_arc_length_input.clear()
|
||||
if hasattr(self, "slot_total_length_input"):
|
||||
self.slot_total_length_input.clear()
|
||||
self.boss_diameter_input.clear()
|
||||
if hasattr(self, "boss_height_input"):
|
||||
self.boss_height_input.clear()
|
||||
self.hole_depth_input.clear()
|
||||
if hasattr(self, "hole_bottom_face_input"):
|
||||
self.hole_bottom_face_input.clear()
|
||||
@@ -1491,7 +1700,7 @@ class WindowCoreMixin:
|
||||
return
|
||||
if self._edit_busy("编辑计算中,暂时不能选择Edge。"):
|
||||
return
|
||||
self._reset_selection(clear_highlight=False)
|
||||
self._reset_selection(clear_highlight=False, clear_info=False)
|
||||
self.selected_kind = "edge"
|
||||
self.selected_edge_id = edge_id
|
||||
self.selected_pick_position = pick_position
|
||||
@@ -1583,8 +1792,7 @@ class WindowCoreMixin:
|
||||
face_ids = [index for index, solid_id in enumerate(self.model.face_solid_ids) if solid_id == target_id]
|
||||
self._highlight_hover_faces(face_ids=face_ids)
|
||||
elif kind == "feature":
|
||||
info = self.model.feature_info(target_id)
|
||||
face_ids = _int_values(info.get("feature_highlight_face_ids")) or [target_id]
|
||||
face_ids = self.model.connected_same_domain_face_ids(target_id) or [target_id]
|
||||
self._highlight_hover_faces(face_ids=face_ids)
|
||||
elif kind == "edge":
|
||||
self._highlight_hover_edge(target_id)
|
||||
@@ -1699,6 +1907,7 @@ class WindowCoreMixin:
|
||||
polydata,
|
||||
color: tuple[float, float, float],
|
||||
opacity: float | None = None,
|
||||
position: tuple[float, float, float] | None = None,
|
||||
) -> None:
|
||||
mapper = vtk.vtkPolyDataMapper()
|
||||
mapper.SetInputData(polydata)
|
||||
@@ -1708,16 +1917,24 @@ class WindowCoreMixin:
|
||||
actor.GetProperty().SetOpacity(opacity if opacity is not None else self.edit_preview_base_opacity)
|
||||
actor.GetProperty().SetSpecular(0.28)
|
||||
actor.GetProperty().SetLineWidth(1)
|
||||
if position is not None:
|
||||
actor.SetPosition(float(position[0]), float(position[1]), float(position[2]))
|
||||
self.edit_preview_actors.append(actor)
|
||||
self.edit_preview_actor = self.edit_preview_actor or actor
|
||||
self.renderer.AddActor(actor)
|
||||
|
||||
def _show_push_pull_preview(self, face_id: int, distance: float) -> None:
|
||||
def _show_push_pull_preview(self, face_id: int, distance: float, plan: dict[str, object] | None = None) -> None:
|
||||
if self.model is None:
|
||||
return
|
||||
self.clear_edit_preview(render=False)
|
||||
try:
|
||||
polydata = self.model.push_pull_preview_polydata(face_id, distance)
|
||||
plan = dict(plan or self.model.push_pull_plan(face_id, distance))
|
||||
scope_face_ids = _int_values(plan.get("push_pull_scope_face_ids")) or [face_id]
|
||||
polydata = self._cached_face_overlay_polydata(face_ids=scope_face_ids, smooth=False)
|
||||
if polydata is None:
|
||||
polydata = self._cached_face_overlay_polydata(face_ids=[face_id], smooth=False)
|
||||
if polydata is None:
|
||||
raise RuntimeError("当前显示网格里没有可复用的选中 Face 预览数据")
|
||||
except Exception as exc:
|
||||
self.statusBar().showMessage(f"推拉预览不可用:{exc}")
|
||||
return
|
||||
@@ -1725,7 +1942,15 @@ class WindowCoreMixin:
|
||||
color = (0.0, 0.86, 0.34)
|
||||
else:
|
||||
color = (1.0, 0.18, 0.06)
|
||||
self._add_edit_preview_actor(polydata, color)
|
||||
outward = plan.get("outward_direction") if isinstance(plan, dict) else None
|
||||
if not isinstance(outward, (tuple, list)) or len(outward) != 3:
|
||||
outward = (0.0, 0.0, 0.0)
|
||||
position = (
|
||||
float(outward[0]) * float(distance),
|
||||
float(outward[1]) * float(distance),
|
||||
float(outward[2]) * float(distance),
|
||||
)
|
||||
self._add_edit_preview_actor(polydata, color, opacity=0.38, position=position)
|
||||
self._start_edit_preview_pulse()
|
||||
self.render_window.Render()
|
||||
|
||||
@@ -1776,6 +2001,19 @@ class WindowCoreMixin:
|
||||
self._start_edit_preview_pulse()
|
||||
self.render_window.Render()
|
||||
|
||||
def _show_cylinder_boss_height_preview(self, face_id: int, target_height: float) -> None:
|
||||
if self.model is None:
|
||||
return
|
||||
self.clear_edit_preview(render=False)
|
||||
try:
|
||||
polydata = self.model.cylindrical_boss_height_preview_polydata(face_id, target_height)
|
||||
except Exception as exc:
|
||||
self.statusBar().showMessage(f"凸台高度调整预览不可用:{exc}")
|
||||
return
|
||||
self._add_edit_preview_actor(polydata, (0.0, 0.72, 0.78), opacity=0.34)
|
||||
self._start_edit_preview_pulse()
|
||||
self.render_window.Render()
|
||||
|
||||
def _show_cylinder_suppress_preview(self, face_id: int) -> None:
|
||||
if self.model is None:
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user