Add Dispatch_V0.1.1
This commit is contained in:
625
Dispatch_V0.1.1/gui/components/model_view/_mv_visual.py
Normal file
625
Dispatch_V0.1.1/gui/components/model_view/_mv_visual.py
Normal file
@@ -0,0 +1,625 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# gui/components/_mv/_mv_visual.py
|
||||
# Маркеры, квадранты, оси, координатные преобразования
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from typing import Optional, Tuple, TYPE_CHECKING
|
||||
from error_logger import log_exception
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from gui.components.model_view_widget import ModelViewWidget
|
||||
|
||||
try:
|
||||
import pyvista as pv
|
||||
_PV = True
|
||||
except ImportError:
|
||||
_PV = False
|
||||
|
||||
|
||||
class VisualHelpersMixin:
|
||||
"""Маркер начала координат, квадранты, оси, world↔display преобразования."""
|
||||
|
||||
# -- маркер начала координат ----------------------------------------------
|
||||
|
||||
_ORIGIN_MARKER_NAME = "__origin_marker"
|
||||
_ORIGIN_PREVIEW_MARKER_NAME = "__origin_preview_marker"
|
||||
|
||||
def show_origin_marker(self: "ModelViewWidget", x: float, y: float, z: float) -> None:
|
||||
if not self._plotter:
|
||||
return
|
||||
try:
|
||||
self._plotter.remove_actor(self._ORIGIN_MARKER_NAME)
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "show_origin_marker", _exc)
|
||||
try:
|
||||
sphere = pv.Sphere(radius=80, center=(x, y, z))
|
||||
self._origin_marker = self._plotter.add_mesh(
|
||||
sphere, color=(1, 1, 0), opacity=0.8, name=self._ORIGIN_MARKER_NAME,
|
||||
)
|
||||
self._plotter.update()
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "show_origin_marker", _exc)
|
||||
def show_origin_preview_marker(self: "ModelViewWidget", x: float, y: float, z: float) -> None:
|
||||
"""Показать динамический маркер origin, следующий за курсором."""
|
||||
if not self._plotter:
|
||||
return
|
||||
try:
|
||||
self._plotter.remove_actor(self._ORIGIN_PREVIEW_MARKER_NAME)
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "show_origin_preview_marker", _exc)
|
||||
try:
|
||||
sphere = pv.Sphere(radius=55, center=(x, y, z))
|
||||
self._origin_preview_marker = self._plotter.add_mesh(
|
||||
sphere,
|
||||
color=(0.2, 0.9, 1.0),
|
||||
opacity=0.7,
|
||||
name=self._ORIGIN_PREVIEW_MARKER_NAME,
|
||||
)
|
||||
self._plotter.update()
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "show_origin_preview_marker", _exc)
|
||||
def clear_origin_marker(self: "ModelViewWidget") -> None:
|
||||
if not self._plotter:
|
||||
return
|
||||
try:
|
||||
self._plotter.remove_actor(self._ORIGIN_MARKER_NAME)
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "clear_origin_marker", _exc)
|
||||
self._origin_marker = None
|
||||
try:
|
||||
if hasattr(self, "_safe_render"):
|
||||
self._safe_render(min_interval_s=0.05)
|
||||
else:
|
||||
self._plotter.render()
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "clear_origin_marker", _exc)
|
||||
def clear_origin_preview_marker(self: "ModelViewWidget") -> None:
|
||||
if not self._plotter:
|
||||
return
|
||||
try:
|
||||
self._plotter.remove_actor(self._ORIGIN_PREVIEW_MARKER_NAME)
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "clear_origin_preview_marker", _exc)
|
||||
self._origin_preview_marker = None
|
||||
try:
|
||||
if hasattr(self, "_safe_render"):
|
||||
self._safe_render(min_interval_s=0.05)
|
||||
else:
|
||||
self._plotter.render()
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "clear_origin_preview_marker", _exc)
|
||||
def clear_origin_markers(self: "ModelViewWidget") -> None:
|
||||
"""Очистить фиксированный и динамический маркеры origin."""
|
||||
self.clear_origin_preview_marker()
|
||||
self.clear_origin_marker()
|
||||
|
||||
# -- угловые точки --------------------------------------------------------
|
||||
|
||||
def set_corner_points(self: "ModelViewWidget", points: list[tuple[float, float, float]]) -> None:
|
||||
self._corner_points = list(points)
|
||||
|
||||
def get_corner_points(self: "ModelViewWidget") -> list[tuple[float, float, float]]:
|
||||
return list(self._corner_points)
|
||||
|
||||
# -- квадранты -------------------------------------------------------------
|
||||
|
||||
def show_quadrants(self: "ModelViewWidget", origin: Tuple[float, float, float], size: float = 6000.0) -> None:
|
||||
if not self._plotter:
|
||||
return
|
||||
self.clear_quadrants()
|
||||
x0, y0, z0 = origin
|
||||
half = size / 2.0
|
||||
# Фиксированная высота квадранта: 100 мм.
|
||||
z_min = z0
|
||||
z_max = z0 + 100.0
|
||||
|
||||
quadrant_bounds = [
|
||||
((x0, x0 + half), (y0, y0 + half)), # 0: ++
|
||||
((x0 - half, x0), (y0, y0 + half)), # 1: -+
|
||||
((x0 - half, x0), (y0 - half, y0)), # 2: --
|
||||
((x0, x0 + half), (y0 - half, y0)), # 3: +-
|
||||
]
|
||||
|
||||
colors = [(0.6, 0.6, 0.6)] * 4
|
||||
|
||||
for idx, (x_range, y_range) in enumerate(quadrant_bounds):
|
||||
x_min, x_max = x_range
|
||||
y_min, y_max = y_range
|
||||
box = pv.Box(bounds=(x_min, x_max, y_min, y_max, z_min, z_max))
|
||||
name = f"__quadrant_plane_{idx}"
|
||||
actor = self._plotter.add_mesh(
|
||||
box,
|
||||
color=colors[idx],
|
||||
opacity=0.12,
|
||||
show_edges=True,
|
||||
edge_color=(0.7, 0.7, 0.7),
|
||||
line_width=1.0,
|
||||
name=name,
|
||||
)
|
||||
try:
|
||||
actor.PickableOn()
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "show_quadrants", _exc)
|
||||
self._quadrant_actors.append(actor)
|
||||
|
||||
for idx, (x_range, y_range) in enumerate(quadrant_bounds):
|
||||
x_min, x_max = x_range
|
||||
y_min, y_max = y_range
|
||||
center_x = (x_min + x_max) / 2.0
|
||||
center_y = (y_min + y_max) / 2.0
|
||||
label_pos = [center_x, center_y, z0 + 100]
|
||||
|
||||
name = f"__quadrant_label_{idx}"
|
||||
label_actor = self._plotter.add_point_labels(
|
||||
[label_pos],
|
||||
[f"Q{idx}"],
|
||||
font_size=72,
|
||||
text_color="yellow",
|
||||
point_color="yellow",
|
||||
point_size=30,
|
||||
render_points_as_spheres=True,
|
||||
always_visible=True,
|
||||
shape_opacity=0.9,
|
||||
name=name,
|
||||
)
|
||||
try:
|
||||
if label_actor is not None:
|
||||
if hasattr(label_actor, "PickableOff"):
|
||||
label_actor.PickableOff()
|
||||
elif hasattr(label_actor, "SetPickable"):
|
||||
label_actor.SetPickable(False)
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "show_quadrants", _exc)
|
||||
self._quadrant_label_actors.append(name)
|
||||
|
||||
self._plotter.update()
|
||||
|
||||
def animate_focus_on_quadrants(
|
||||
self: "ModelViewWidget",
|
||||
*,
|
||||
duration_ms: int = 420,
|
||||
margin_factor: float = 1.2,
|
||||
) -> bool:
|
||||
"""Плавно сфокусировать камеру так, чтобы текущие квадранты целиком были в кадре."""
|
||||
if not self._plotter:
|
||||
return False
|
||||
cam = getattr(self._plotter, "camera", None)
|
||||
if cam is None:
|
||||
return False
|
||||
actors = list(getattr(self, "_quadrant_actors", []) or [])
|
||||
if not actors:
|
||||
return False
|
||||
try:
|
||||
min_x = float("inf")
|
||||
max_x = float("-inf")
|
||||
min_y = float("inf")
|
||||
max_y = float("-inf")
|
||||
min_z = float("inf")
|
||||
max_z = float("-inf")
|
||||
for actor in actors:
|
||||
if actor is None or not hasattr(actor, "GetBounds"):
|
||||
continue
|
||||
b = actor.GetBounds()
|
||||
if not b or len(b) < 6:
|
||||
continue
|
||||
min_x = min(min_x, float(b[0]))
|
||||
max_x = max(max_x, float(b[1]))
|
||||
min_y = min(min_y, float(b[2]))
|
||||
max_y = max(max_y, float(b[3]))
|
||||
min_z = min(min_z, float(b[4]))
|
||||
max_z = max(max_z, float(b[5]))
|
||||
if not all(math.isfinite(v) for v in (min_x, max_x, min_y, max_y, min_z, max_z)):
|
||||
return False
|
||||
cx = (min_x + max_x) * 0.5
|
||||
cy = (min_y + max_y) * 0.5
|
||||
sx = max(1.0, max_x - min_x)
|
||||
sy = max(1.0, max_y - min_y)
|
||||
sz = max(1.0, max_z - min_z)
|
||||
span = max(sx, sy, sz) * max(1.0, float(margin_factor))
|
||||
cz = min_z + (sz * 0.55)
|
||||
|
||||
start_pos = tuple(float(v) for v in cam.GetPosition())
|
||||
start_focal = tuple(float(v) for v in cam.GetFocalPoint())
|
||||
start_up = tuple(float(v) for v in cam.GetViewUp())
|
||||
|
||||
vx, vy, vz = 1.0, -1.0, 0.72
|
||||
norm = (vx * vx + vy * vy + vz * vz) ** 0.5
|
||||
vx, vy, vz = vx / norm, vy / norm, vz / norm
|
||||
ux, uy, uz = 0.0, 0.0, 1.0
|
||||
distance = max(3000.0, span * 2.35)
|
||||
target_focal = (cx, cy, cz)
|
||||
target_pos = (
|
||||
cx + vx * distance,
|
||||
cy + vy * distance,
|
||||
cz + vz * distance + (sz * 0.08),
|
||||
)
|
||||
|
||||
if hasattr(self, "_animate_camera_transition"):
|
||||
self._animate_camera_transition(
|
||||
start_pos=start_pos,
|
||||
start_focal=start_focal,
|
||||
start_up=start_up,
|
||||
target_pos=target_pos,
|
||||
target_focal=target_focal,
|
||||
target_up=(ux, uy, uz),
|
||||
duration_ms=int(duration_ms),
|
||||
steps=18,
|
||||
)
|
||||
else:
|
||||
cam.SetPosition(*target_pos)
|
||||
cam.SetFocalPoint(*target_focal)
|
||||
if hasattr(cam, "SetViewUp"):
|
||||
cam.SetViewUp(ux, uy, uz)
|
||||
if hasattr(self, "_reset_camera_clipping_range"):
|
||||
self._reset_camera_clipping_range()
|
||||
self._plotter.update()
|
||||
return True
|
||||
except Exception as exc:
|
||||
log_exception(__name__, "animate_focus_on_quadrants", exc)
|
||||
return False
|
||||
|
||||
def show_grid_step_quadrants(
|
||||
self: "ModelViewWidget",
|
||||
origin: Tuple[float, float, float],
|
||||
direction: Tuple[float, float],
|
||||
step_values: list[int],
|
||||
cell_size: float = 3000.0,
|
||||
height: float = 100.0,
|
||||
) -> None:
|
||||
"""Показать 6 площадок выбора шага сетки (3x2) в положительных локальных координатах."""
|
||||
if not self._plotter:
|
||||
return
|
||||
self.clear_quadrants()
|
||||
x0, y0, z0 = origin
|
||||
dir_x = 1.0 if float(direction[0]) >= 0.0 else -1.0
|
||||
dir_y = 1.0 if float(direction[1]) >= 0.0 else -1.0
|
||||
z_min = float(z0)
|
||||
z_max = float(z0) + float(height)
|
||||
grid_actors = list(getattr(self, "_quadrant_grid_actors", []) or [])
|
||||
for actor in grid_actors:
|
||||
try:
|
||||
self._plotter.remove_actor(actor)
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "show_grid_step_quadrants", _exc)
|
||||
self._quadrant_grid_actors = []
|
||||
|
||||
def _add_segment(
|
||||
pts: list[list[float]],
|
||||
cells: list[int],
|
||||
p1: tuple[float, float, float],
|
||||
p2: tuple[float, float, float],
|
||||
) -> None:
|
||||
i1 = len(pts)
|
||||
pts.append([float(p1[0]), float(p1[1]), float(p1[2])])
|
||||
i2 = len(pts)
|
||||
pts.append([float(p2[0]), float(p2[1]), float(p2[2])])
|
||||
cells.extend([2, i1, i2])
|
||||
|
||||
for idx, step in enumerate(step_values):
|
||||
row = idx // 3
|
||||
col = idx % 3
|
||||
lx0 = float(col) * float(cell_size)
|
||||
lx1 = float(col + 1) * float(cell_size)
|
||||
ly0 = float(row) * float(cell_size)
|
||||
ly1 = float(row + 1) * float(cell_size)
|
||||
wx0 = float(x0) + lx0 * dir_x
|
||||
wx1 = float(x0) + lx1 * dir_x
|
||||
wy0 = float(y0) + ly0 * dir_y
|
||||
wy1 = float(y0) + ly1 * dir_y
|
||||
x_min, x_max = (min(wx0, wx1), max(wx0, wx1))
|
||||
y_min, y_max = (min(wy0, wy1), max(wy0, wy1))
|
||||
|
||||
box = pv.Box(bounds=(x_min, x_max, y_min, y_max, z_min, z_max))
|
||||
name = f"__quadrant_plane_{idx}"
|
||||
actor = self._plotter.add_mesh(
|
||||
box,
|
||||
color=(0.6, 0.6, 0.6),
|
||||
opacity=0.12,
|
||||
show_edges=True,
|
||||
edge_color=(0.7, 0.7, 0.7),
|
||||
line_width=1.0,
|
||||
name=name,
|
||||
)
|
||||
try:
|
||||
actor.PickableOn()
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "_add_segment", _exc)
|
||||
self._quadrant_actors.append(actor)
|
||||
|
||||
# Явная разметка сеткой с шагом площадки (поверх плоскости).
|
||||
line_points: list[list[float]] = []
|
||||
line_cells: list[int] = []
|
||||
z_grid = float(z_max) + 0.8
|
||||
step_size = max(10.0, float(step))
|
||||
x_pos = float(x_min)
|
||||
while x_pos <= float(x_max) + 1e-6:
|
||||
_add_segment(line_points, line_cells, (x_pos, y_min, z_grid), (x_pos, y_max, z_grid))
|
||||
x_pos += step_size
|
||||
y_pos = float(y_min)
|
||||
while y_pos <= float(y_max) + 1e-6:
|
||||
_add_segment(line_points, line_cells, (x_min, y_pos, z_grid), (x_max, y_pos, z_grid))
|
||||
y_pos += step_size
|
||||
if line_points and line_cells:
|
||||
try:
|
||||
grid_lines = pv.PolyData(line_points)
|
||||
grid_lines.lines = line_cells
|
||||
grid_actor = self._plotter.add_mesh(
|
||||
grid_lines,
|
||||
color=(0.0, 0.95, 1.0),
|
||||
line_width=2.0,
|
||||
opacity=0.95,
|
||||
lighting=False,
|
||||
)
|
||||
self._quadrant_grid_actors.append(grid_actor)
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "_add_segment", _exc)
|
||||
label_pos = [(x_min + x_max) / 2.0, (y_min + y_max) / 2.0, float(z0) + float(height)]
|
||||
label_name = f"__quadrant_label_{idx}"
|
||||
label_actor = self._plotter.add_point_labels(
|
||||
[label_pos],
|
||||
[f"{int(step)} мм"],
|
||||
font_size=56,
|
||||
text_color="yellow",
|
||||
point_color="yellow",
|
||||
point_size=24,
|
||||
render_points_as_spheres=True,
|
||||
always_visible=True,
|
||||
shape_opacity=0.9,
|
||||
name=label_name,
|
||||
)
|
||||
try:
|
||||
if label_actor is not None:
|
||||
if hasattr(label_actor, "PickableOff"):
|
||||
label_actor.PickableOff()
|
||||
elif hasattr(label_actor, "SetPickable"):
|
||||
label_actor.SetPickable(False)
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "_add_segment", _exc)
|
||||
self._quadrant_label_actors.append(label_name)
|
||||
|
||||
self._plotter.update()
|
||||
|
||||
def highlight_quadrant(self: "ModelViewWidget", index: int) -> None:
|
||||
if not self._plotter:
|
||||
return
|
||||
for i, actor in enumerate(self._quadrant_actors):
|
||||
try:
|
||||
if i == index:
|
||||
actor.GetProperty().SetOpacity(1.0)
|
||||
actor.GetProperty().SetColor(1.0, 1.0, 0.0)
|
||||
else:
|
||||
actor.GetProperty().SetOpacity(0.12)
|
||||
actor.GetProperty().SetColor(0.6, 0.6, 0.6)
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "highlight_quadrant", _exc)
|
||||
try:
|
||||
if hasattr(self, "_safe_render"):
|
||||
self._safe_render(min_interval_s=0.01)
|
||||
else:
|
||||
self._plotter.render()
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "highlight_quadrant", _exc)
|
||||
def clear_quadrants(self: "ModelViewWidget") -> None:
|
||||
if not self._plotter:
|
||||
return
|
||||
for actor in self._quadrant_actors:
|
||||
try:
|
||||
self._plotter.remove_actor(actor)
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "clear_quadrants", _exc)
|
||||
self._quadrant_actors = []
|
||||
|
||||
for name in self._quadrant_label_actors:
|
||||
try:
|
||||
self._plotter.remove_actor(name)
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "clear_quadrants", _exc)
|
||||
self._quadrant_label_actors = []
|
||||
for actor in list(getattr(self, "_quadrant_grid_actors", []) or []):
|
||||
try:
|
||||
self._plotter.remove_actor(actor)
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "clear_quadrants", _exc)
|
||||
self._quadrant_grid_actors = []
|
||||
|
||||
try:
|
||||
if hasattr(self, "_safe_render"):
|
||||
self._safe_render(min_interval_s=0.05)
|
||||
else:
|
||||
self._plotter.render()
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "clear_quadrants", _exc)
|
||||
# -- оси -------------------------------------------------------------------
|
||||
|
||||
def show_axes(self: "ModelViewWidget", origin: Tuple[float, float, float], dir_x: float, dir_y: float) -> None:
|
||||
if not self._plotter:
|
||||
return
|
||||
for actor in self._axes_actors:
|
||||
try:
|
||||
self._plotter.remove_actor(actor)
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "show_axes", _exc)
|
||||
self._axes_actors = []
|
||||
x0, y0, z0 = origin
|
||||
axes = [
|
||||
((dir_x, 0, 0), (1, 0, 0), "X"), # X: red
|
||||
((0, dir_y, 0), (0, 1, 0), "Y"), # Y: green
|
||||
((0, 0, 1), (0, 0, 1), "Z"), # Z-up: blue
|
||||
]
|
||||
axis_len = 1500.0
|
||||
label_offset = 1700.0
|
||||
for direction, color, label in axes:
|
||||
arrow = pv.Arrow(start=(x0, y0, z0), direction=direction, scale=axis_len)
|
||||
actor = self._plotter.add_mesh(
|
||||
arrow,
|
||||
color=color,
|
||||
opacity=1.0,
|
||||
lighting=False,
|
||||
ambient=1.0,
|
||||
diffuse=0.0,
|
||||
specular=0.0,
|
||||
)
|
||||
self._axes_actors.append(actor)
|
||||
try:
|
||||
dx, dy, dz = float(direction[0]), float(direction[1]), float(direction[2])
|
||||
label_pos = [[x0 + dx * label_offset, y0 + dy * label_offset, z0 + dz * label_offset]]
|
||||
label_actor = self._plotter.add_point_labels(
|
||||
label_pos,
|
||||
[label],
|
||||
font_size=36,
|
||||
text_color=color,
|
||||
point_size=1,
|
||||
shape_opacity=0.0,
|
||||
always_visible=True,
|
||||
)
|
||||
if label_actor is not None:
|
||||
self._axes_actors.append(label_actor)
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "show_axes", _exc)
|
||||
try:
|
||||
if hasattr(self, "_reset_camera_clipping_range"):
|
||||
self._reset_camera_clipping_range()
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "show_axes", _exc)
|
||||
try:
|
||||
if hasattr(self, "_safe_render"):
|
||||
self._safe_render(min_interval_s=0.0)
|
||||
else:
|
||||
self._plotter.render()
|
||||
except Exception as _exc:
|
||||
log_exception(__name__, "show_axes", _exc)
|
||||
# -- преобразования координат ----------------------------------------------
|
||||
|
||||
def world_to_display(self: "ModelViewWidget", x: float, y: float, z: float) -> Optional[Tuple[float, float]]:
|
||||
if not self._plotter or not self._plotter.renderer:
|
||||
return None
|
||||
try:
|
||||
self._plotter.renderer.SetWorldPoint(x, y, z, 1.0)
|
||||
self._plotter.renderer.WorldToDisplay()
|
||||
dx, dy, _ = self._plotter.renderer.GetDisplayPoint()
|
||||
return (dx, dy)
|
||||
except Exception as exc:
|
||||
log_exception(__name__, "world_to_display", exc)
|
||||
return None
|
||||
|
||||
def pick_world(self: "ModelViewWidget", sx: float, sy: float) -> Optional[Tuple[float, float, float]]:
|
||||
if not self._plotter or not self._plotter.renderer or not self._plotter.picker:
|
||||
return None
|
||||
try:
|
||||
self._plotter.picker.Pick(sx, sy, 0, self._plotter.renderer)
|
||||
pos = self._plotter.picker.GetPickPosition()
|
||||
if pos is None:
|
||||
return None
|
||||
x, y, z = pos[0], pos[1], pos[2]
|
||||
if all(math.isfinite(v) for v in (x, y, z)):
|
||||
return (x, y, z)
|
||||
except Exception as exc:
|
||||
log_exception(__name__, "pick_world", exc)
|
||||
return None
|
||||
return None
|
||||
|
||||
def screen_to_world_on_plane(
|
||||
self: "ModelViewWidget", sx: float, sy: float, z_plane: float,
|
||||
) -> Optional[Tuple[float, float, float]]:
|
||||
"""Преобразовать экранные координаты в мировые на плоскости Z=z_plane."""
|
||||
if not self._plotter or not self._plotter.renderer:
|
||||
return None
|
||||
try:
|
||||
renderer = self._plotter.renderer
|
||||
renderer.SetDisplayPoint(sx, sy, 0)
|
||||
renderer.DisplayToWorld()
|
||||
p0 = renderer.GetWorldPoint()
|
||||
renderer.SetDisplayPoint(sx, sy, 1)
|
||||
renderer.DisplayToWorld()
|
||||
p1 = renderer.GetWorldPoint()
|
||||
if p0[3] == 0 or p1[3] == 0:
|
||||
return None
|
||||
p0 = (p0[0] / p0[3], p0[1] / p0[3], p0[2] / p0[3])
|
||||
p1 = (p1[0] / p1[3], p1[1] / p1[3], p1[2] / p1[3])
|
||||
|
||||
dz = p1[2] - p0[2]
|
||||
if dz == 0:
|
||||
return None
|
||||
t = (z_plane - p0[2]) / dz
|
||||
wx = p0[0] + t * (p1[0] - p0[0])
|
||||
wy = p0[1] + t * (p1[1] - p0[1])
|
||||
return (wx, wy, z_plane)
|
||||
except Exception as exc:
|
||||
log_exception(__name__, "screen_to_world_on_plane", exc)
|
||||
return None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Module workflow notes
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1) Задача модуля:
|
||||
# Визуальные вспомогательные функции: маркеры, оси, перевод координат, подбор точек.
|
||||
#
|
||||
# 2) Последовательность действий и вызовов:
|
||||
# A. Класс VisualHelpersMixin: точки входа
|
||||
# Публичные методы сценария:
|
||||
# - VisualHelpersMixin.show_origin_marker(...)
|
||||
# - VisualHelpersMixin.show_origin_preview_marker(...)
|
||||
# - VisualHelpersMixin.clear_origin_marker(...)
|
||||
# - VisualHelpersMixin.clear_origin_preview_marker(...)
|
||||
# - VisualHelpersMixin.clear_origin_markers(...)
|
||||
# - VisualHelpersMixin.set_corner_points(...)
|
||||
# - VisualHelpersMixin.get_corner_points(...)
|
||||
# - VisualHelpersMixin.show_quadrants(...)
|
||||
# - VisualHelpersMixin.highlight_quadrant(...)
|
||||
# - VisualHelpersMixin.clear_quadrants(...)
|
||||
# - VisualHelpersMixin.show_axes(...)
|
||||
# - VisualHelpersMixin.world_to_display(...)
|
||||
# - VisualHelpersMixin.pick_world(...)
|
||||
# - VisualHelpersMixin.screen_to_world_on_plane(...)
|
||||
#
|
||||
# B. VisualHelpersMixin: запуск и настройка:
|
||||
# VisualHelpersMixin.set_corner_points(...)
|
||||
# Назначение: устанавливает corner points в рамках текущего сценария модуля.
|
||||
#
|
||||
# C. VisualHelpersMixin: основной сценарий:
|
||||
# VisualHelpersMixin.show_origin_marker(...)
|
||||
# Назначение: показывает origin marker в рамках текущего сценария модуля.
|
||||
# VisualHelpersMixin.show_origin_preview_marker(...)
|
||||
# Назначение: показывает динамический маркер origin в рамках текущего сценария модуля.
|
||||
# VisualHelpersMixin.show_quadrants(...)
|
||||
# Назначение: показывает quadrants в рамках текущего сценария модуля.
|
||||
# Последовательность внутренних вызовов:
|
||||
# -> VisualHelpersMixin.clear_quadrants(...)
|
||||
# VisualHelpersMixin.show_axes(...)
|
||||
# Назначение: показывает axes в рамках текущего сценария модуля.
|
||||
# VisualHelpersMixin.pick_world(...)
|
||||
# Назначение: выполняет шаг "pick world" в рамках текущего сценария модуля.
|
||||
#
|
||||
# D. VisualHelpersMixin: завершение и очистка:
|
||||
# VisualHelpersMixin.clear_origin_marker(...)
|
||||
# Назначение: очищает origin marker в рамках текущего сценария модуля.
|
||||
# VisualHelpersMixin.clear_origin_preview_marker(...)
|
||||
# Назначение: очищает origin preview marker в рамках текущего сценария модуля.
|
||||
# VisualHelpersMixin.clear_origin_markers(...)
|
||||
# Назначение: очищает все маркеры origin в рамках текущего сценария модуля.
|
||||
# VisualHelpersMixin.clear_quadrants(...)
|
||||
# Назначение: очищает quadrants в рамках текущего сценария модуля.
|
||||
#
|
||||
# E. VisualHelpersMixin: вспомогательные расчёты:
|
||||
# VisualHelpersMixin.get_corner_points(...)
|
||||
# Назначение: возвращает corner points в рамках текущего сценария модуля.
|
||||
# VisualHelpersMixin.highlight_quadrant(...)
|
||||
# Назначение: подсвечивает quadrant в рамках текущего сценария модуля.
|
||||
# VisualHelpersMixin.world_to_display(...)
|
||||
# Назначение: выполняет шаг "world to display" в рамках текущего сценария модуля.
|
||||
# VisualHelpersMixin.screen_to_world_on_plane(...)
|
||||
# Назначение: Преобразовать экранные координаты в мировые на плоскости Z=z_plane.
|
||||
#
|
||||
# 3) Важные ограничения и инварианты:
|
||||
# - Модуль выполняется в составе ModelViewWidget и использует согласованные поля состояния self._... .
|
||||
# - Все операции с актёрами и камерой выполняются только при валидном self._plotter с безопасной обработкой исключений.
|
||||
# - Геометрическая визуализация зависит от pyvista/vtk; при недоступности модуль обязан завершать шаг без падения сценария.
|
||||
# - Межмодульная связность: только через фасад model_view; прямые обращения между zone, rack, shelf, cell запрещены.
|
||||
# - Очистка состояния должна быть идемпотентной: повторный вызов не меняет корректное состояние в ошибочное.
|
||||
#
|
||||
# 4) Правило сопровождения:
|
||||
# - Любое изменение сценария должно сопровождаться обновлением этого блока с сохранением фактического порядка вызовов.
|
||||
# - При добавлении метода указывать его место в цепочке сценария (запуск, основной шаг, завершение, вспомогательная логика).
|
||||
Reference in New Issue
Block a user