Add Dispatch_V0.1.1
This commit is contained in:
272
Dispatch_V0.1.1/gui/components/model_view_widget.py
Normal file
272
Dispatch_V0.1.1/gui/components/model_view_widget.py
Normal file
@@ -0,0 +1,272 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# gui/components/model_view_widget.py
|
||||
"""Виджет отображения и управления 3D-моделью с поддержкой зон.
|
||||
|
||||
Данный файл использует фасад composition-слоя `gui.components.model_view.contour`
|
||||
и миксины с функционалом, которые внедрены в `ModelViewWidget`.
|
||||
"""
|
||||
|
||||
from typing import Optional, Tuple, Callable
|
||||
|
||||
from gui.containers import VContainer, SContainer
|
||||
from PySide6.QtCore import Signal
|
||||
from gui.theme_bus import theme_bus
|
||||
from error_logger import log_exception
|
||||
|
||||
from gui.components.model_view._interaction_scenario import InteractionManager
|
||||
from gui.components.model_view._scenario_camera import CameraScenario
|
||||
from gui.components.model_view._scenario_facility_browse import FacilityBrowseScenario
|
||||
from gui.components.model_view.contour import (
|
||||
ModelViewContourFacade,
|
||||
LegacyContourBindingComponent,
|
||||
)
|
||||
|
||||
from gui.components.model_view import (
|
||||
ModelLoadingMixin,
|
||||
ZoneManagementMixin,
|
||||
InteractionMixin,
|
||||
VisualHelpersMixin,
|
||||
GridCoreMixin,
|
||||
DimensionLinesMixin,
|
||||
RackPlacementMixin,
|
||||
ScenePresentationMixin,
|
||||
SceneModesMixin,
|
||||
RackCameraTransitionMixin,
|
||||
ZoneCameraTransitionMixin,
|
||||
RackPlacementIOMixin,
|
||||
)
|
||||
|
||||
|
||||
class ModelViewWidget(
|
||||
InteractionMixin,
|
||||
ModelLoadingMixin,
|
||||
ZoneManagementMixin,
|
||||
VisualHelpersMixin,
|
||||
GridCoreMixin,
|
||||
DimensionLinesMixin,
|
||||
RackPlacementMixin,
|
||||
ScenePresentationMixin,
|
||||
SceneModesMixin,
|
||||
RackCameraTransitionMixin,
|
||||
ZoneCameraTransitionMixin,
|
||||
RackPlacementIOMixin,
|
||||
SContainer,
|
||||
):
|
||||
"""Виджет для отображения 3D моделей помещения и зон."""
|
||||
|
||||
# Сигналы
|
||||
error_occurred = Signal(str)
|
||||
zone_selected = Signal(str) # ид. зоны
|
||||
zone_double_clicked = Signal(str) # двойной ЛКМ по зоне
|
||||
camera_mode_changed = Signal(bool) # True = заблокирована
|
||||
grid_volume_changed = Signal(float, float, float) # ширина, глубина, высота
|
||||
contour_ready_changed = Signal(bool) # контур ортогонален и пригоден для объёма
|
||||
rack_layout_changed = Signal(str) # zone_id
|
||||
rack_slot_visibility_changed = Signal(str, str, bool) # rack_id, slot_id, occupied
|
||||
rack_selected_changed = Signal(str) # ид. стеллажа или ""
|
||||
rack_double_clicked = Signal(str, str) # rack_id, zone_id
|
||||
shelf_slot_selected = Signal(str, str) # rack_id, slot_id
|
||||
shelf_slot_double_clicked = Signal(str, str, int) # rack_id, slot_id, shelf_index
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
width_percent: int | None = None,
|
||||
height_percent: int | None = None,
|
||||
margin: int | tuple[int, int, int, int] = 0,
|
||||
content_fit: bool = True,
|
||||
parent=None,
|
||||
style: str | None = None,
|
||||
active_style: str | None = None,
|
||||
is_active: bool | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
width_percent=width_percent,
|
||||
height_percent=height_percent,
|
||||
content_fit=content_fit,
|
||||
parent=parent,
|
||||
style=style,
|
||||
active_style=active_style,
|
||||
is_active=is_active,
|
||||
)
|
||||
self._content_margin = margin
|
||||
self._content_fit = content_fit
|
||||
self._plotter = None
|
||||
self._models_loaded = False
|
||||
self._camera_locked = False
|
||||
self._theme = "dark"
|
||||
|
||||
# Данные моделей
|
||||
self._floor_mesh = None
|
||||
self._walls_mesh = None
|
||||
self._ceiling_mesh = None
|
||||
self._truss_mesh = None
|
||||
self._rack_meshes: list = []
|
||||
self._zones: dict = {} # ид. зоны -> mesh
|
||||
self._zone_data: dict = {} # zone_id -> (min_x, max_x, min_y, max_y, min_z, max_z)
|
||||
self._zone_polygons: dict = {} # ид. зоны -> list[(x, y)]
|
||||
self._zone_heights: dict = {} # zone_id -> (start_z, height)
|
||||
self._model_actors: dict = {} # ключ -> actor
|
||||
self._missing_models: set = set()
|
||||
|
||||
# Интерактивное размещение
|
||||
self._preview_zone = None
|
||||
self._current_zone_size = 100 # мм
|
||||
self._current_zone_z_size = 100 # мм, шаг по высоте Z
|
||||
self._current_zone_color = "#FF6B6B80"
|
||||
|
||||
# Ограничивающий параллелепипед
|
||||
self._room_bounds = None # (min_x, max_x, min_y, max_y, min_z, max_z)
|
||||
|
||||
# Режим выбора зон с сеткой
|
||||
self._zone_selection_mode = False
|
||||
self._zone_pick_enabled = True
|
||||
self._selected_zone_highlight_id: Optional[str] = None
|
||||
self._selected_zone_original_opacity: Optional[float] = None
|
||||
self._selected_zone_original_visibility: Optional[bool] = None
|
||||
self._hover_highlighted_zone_id: Optional[str] = None
|
||||
self._hover_zone_original_opacity: Optional[float] = None
|
||||
self._hover_zone_original_edges: bool = False
|
||||
self._hover_zone_original_visibility: Optional[bool] = None
|
||||
self._hover_zone_contour_original_color: Optional[tuple] = None
|
||||
self._grid_meshes: list = []
|
||||
self._selected_cells: set = set()
|
||||
self._grid_cells: dict = {}
|
||||
self._grid_origin = (0, 0, 0)
|
||||
self._selected_height = 0
|
||||
self._selection_anchor = None # (min_x, min_y)
|
||||
self._contour_zone_overlay_enabled = False
|
||||
self._contour_zone_mode = None
|
||||
self._contour_zone_id = None
|
||||
self._contour_ignore_zone_id = None
|
||||
self._contour_ready = False
|
||||
self._grid_plane_z_override = None
|
||||
|
||||
# Кэш сетки — повторное использование сетки без пересоздания
|
||||
self._grid_ready = False
|
||||
self._grid_cached_cell_size = 0
|
||||
self._grid_cached_z_size = 0
|
||||
|
||||
# Атрибуты контура/сетки — инициализация для стабильности
|
||||
self._grid_nodes: list = []
|
||||
self._grid_surface_meshes: list = []
|
||||
self._grid_surface_nodes: list[tuple[float, float]] = []
|
||||
self._contour_points: list = []
|
||||
self._final_contour_points: list = []
|
||||
self._contour_points_actor = None
|
||||
self._contour_lines_actor = None
|
||||
self._contour_drag_active = False
|
||||
self._contour_drag_point_index: Optional[int] = None
|
||||
self._contour_drag_moved = False
|
||||
self._selection_start_cell = None
|
||||
self._volume_locked_from_contour = False
|
||||
self._last_volume_start_height = 0.0
|
||||
|
||||
# Перетаскивание
|
||||
self._dragging = False
|
||||
self._drag_start_y = 0
|
||||
self._cam_rotate_active = False
|
||||
self._cam_pan_active = False
|
||||
self._cam_last_pos: Optional[Tuple[float, float]] = None
|
||||
self._last_safe_render_ts = 0.0
|
||||
self._custom_click_handler: Optional[Callable[[float, float, float], bool]] = None
|
||||
self._hover_handler: Optional[Callable[[float, float, float], None]] = None
|
||||
self._hover_screen_handler: Optional[Callable[[float, float], None]] = None
|
||||
self._origin_marker = None
|
||||
self._origin_preview_marker = None
|
||||
self._quadrant_actors: list = []
|
||||
self._quadrant_label_actors: list = []
|
||||
self._axes_actors: list = []
|
||||
self._last_hover_point: Optional[Tuple[float, float, float]] = None
|
||||
self._corner_points: list[tuple[float, float, float]] = []
|
||||
self._measure_ref_point: tuple[float, float, float] = (0.0, 0.0, 0.0)
|
||||
self._measure_direction: tuple[float, float] = (1.0, 1.0)
|
||||
self._ignore_next_plotter_click = False
|
||||
|
||||
# Размерные линии
|
||||
self.init_dimension_lines()
|
||||
self.init_rack_placement()
|
||||
|
||||
# Менеджер сценариев взаимодействия
|
||||
self._interaction_manager = InteractionManager(self)
|
||||
self._interaction_manager.set_camera_scenario(CameraScenario())
|
||||
self._interaction_manager.push(FacilityBrowseScenario())
|
||||
self._legacy_contour_binding = LegacyContourBindingComponent(self)
|
||||
self._legacy_contour_binding.install()
|
||||
self.contour_facade = ModelViewContourFacade(self)
|
||||
|
||||
self.setup_ui()
|
||||
theme_bus.theme_changed.connect(self.set_theme)
|
||||
|
||||
# -- Интерфейс -------------------------------------------------------------------
|
||||
|
||||
def setup_ui(self):
|
||||
"""Настройка интерфейса."""
|
||||
self._main_container = VContainer(
|
||||
margin=self._content_margin,
|
||||
content_fit=self._content_fit,
|
||||
parent=self,
|
||||
)
|
||||
|
||||
# -- простые сеттеры / свойства ------------------------------------------
|
||||
|
||||
def show_error(self, message):
|
||||
"""Показать сообщение об ошибке."""
|
||||
self.error_occurred.emit(message)
|
||||
|
||||
def update_scene(self) -> None:
|
||||
"""Перерисовать 3D-сцену."""
|
||||
if self._plotter:
|
||||
self._plotter.update()
|
||||
|
||||
def set_ignore_next_click(self, ignore: bool) -> None:
|
||||
"""Пропустить следующий клик по plotter."""
|
||||
self._ignore_next_plotter_click = bool(ignore)
|
||||
|
||||
def is_camera_locked(self) -> bool:
|
||||
"""Заблокирована ли камера."""
|
||||
return bool(self._camera_locked)
|
||||
|
||||
def is_scenario_active(self, name: str) -> bool:
|
||||
"""Проверить, активен ли сценарий взаимодействия по имени."""
|
||||
mgr = getattr(self, "_interaction_manager", None)
|
||||
if mgr is not None:
|
||||
return mgr.is_active(name)
|
||||
return False
|
||||
|
||||
def pop_interaction_scenario(self, name: str) -> None:
|
||||
"""Снять сценарий взаимодействия по имени."""
|
||||
if self._interaction_manager is not None:
|
||||
self._interaction_manager.pop_by_name(name)
|
||||
|
||||
def set_theme(self, theme: str) -> None:
|
||||
theme = (theme or "").strip().lower()
|
||||
if theme not in ("dark", "light"):
|
||||
return
|
||||
if self._theme == theme:
|
||||
return
|
||||
self._theme = theme
|
||||
if hasattr(self, "_apply_grid_theme"):
|
||||
try:
|
||||
self._apply_grid_theme()
|
||||
except Exception as e:
|
||||
log_exception(__name__, "set_theme", e)
|
||||
|
||||
def set_zone_pick_enabled(self, enabled: bool) -> None:
|
||||
"""Разрешить выбор существующих зон по клику."""
|
||||
self._zone_pick_enabled = bool(enabled)
|
||||
if not enabled:
|
||||
self.clear_selected_zone_highlight()
|
||||
self._unhighlight_hover_zone()
|
||||
|
||||
def set_current_zone_color(self, color: str) -> None:
|
||||
"""Установить цвет предпросмотра зоны (#RRGGBBAA)."""
|
||||
self._current_zone_color = color
|
||||
|
||||
@property
|
||||
def room_bounds(self):
|
||||
return self._room_bounds
|
||||
|
||||
@property
|
||||
def zone_selection_mode(self) -> bool:
|
||||
"""Возвращает режим выбора зоны в 3D сцене."""
|
||||
return self._zone_selection_mode
|
||||
Reference in New Issue
Block a user