From 2c8b317c420dbc62a8fe2eab5376bd55b1ec1954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=A1=D0=BE?= =?UTF-8?q?=D0=BB=D0=BE=D0=B2=D1=8C=D0=B5=D0=B2?= Date: Mon, 8 Jun 2026 13:59:53 +0300 Subject: [PATCH] =?UTF-8?q?fix(editor):=20=D0=B7=D0=B0=D0=B6=D0=B8=D0=BC?= =?UTF-8?q?=20=D0=BA=D1=80=D0=B5=D0=BD=D0=B0=20=D0=BC=D0=B0=D1=80=D1=88?= =?UTF-8?q?=D1=80=D1=83=D1=82=D0=B0=20=D0=BA=20=C2=B145=C2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Бэкенд считает геометрию видимости/маршрута только в |крен| ≤ 45°, поэтому предел снижен с 89° до 45°. Введён общий clampRoll (зажим + округление до 0.1°), применён при ручной правке крена (кнопки ±, прямой ввод) и при загрузке сохранённого маршрута в окно построения (легаси-маршрут вне ±45° зажимается). --- .../src/features/tgu-editor/TguEditorTab.tsx | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/services/pcp-tgu-ops-ui/src/features/tgu-editor/TguEditorTab.tsx b/services/pcp-tgu-ops-ui/src/features/tgu-editor/TguEditorTab.tsx index f505ca2..a4335e6 100644 --- a/services/pcp-tgu-ops-ui/src/features/tgu-editor/TguEditorTab.tsx +++ b/services/pcp-tgu-ops-ui/src/features/tgu-editor/TguEditorTab.tsx @@ -74,8 +74,15 @@ const SHOOT_WINDOW_MS = 6 * 60 * 1000; const DEFAULT_CAPTURE_ANGLE_DEG = 1.5; /** Дефолтная длительность строящегося маршрута съёмки (мс). */ const DEFAULT_ROUTE_DURATION_MS = 30_000; -/** Предел крена (gamma) маршрута при ручной правке, градусы. */ -const MAX_ROUTE_ROLL_DEG = 89; +/** + * Предел крена (gamma) маршрута, градусы. Параметры видимости/маршрута бэкенд считает только + * в диапазоне |крен| ≤ 45°; за его пределами геометрия маршрута не определена, поэтому жёстко + * зажимаем здесь — и при ручной правке, и при загрузке сохранённого маршрута в окно построения. + */ +const MAX_ROUTE_ROLL_DEG = 45; +/** Крен с зажимом в допустимый диапазон и округлением до 1 знака (как при ручном вводе). */ +const clampRoll = (deg: number): number => + Math.round(Math.max(-MAX_ROUTE_ROLL_DEG, Math.min(MAX_ROUTE_ROLL_DEG, deg)) * 10) / 10; /** Допустимый выход окна маршрута за пределы окна видимости, мс (±30 минут). */ const ROUTE_TIME_MARGIN_MS = 30 * 60_000; const EDITOR_MAP_LAYERS: Tgu2DMapLayersState = { @@ -421,15 +428,15 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange, if (prefill) { editShootPrefillRef.current = null; setRouteWindow({ startMs: prefill.startMs, endMs: prefill.endMs }); - setRouteRoll(Math.round(prefill.roll * 10) / 10); + // Сохранённый маршрут мог быть с креном вне ±45° (легаси) — зажимаем при загрузке в окно. + setRouteRoll(clampRoll(prefill.roll)); return; } const passDurationMs = selectedShootPass.endMs - selectedShootPass.startMs; const half = Math.min(DEFAULT_ROUTE_DURATION_MS, passDurationMs) / 2; const mid = (selectedShootPass.startMs + selectedShootPass.endMs) / 2; setRouteWindow({ startMs: mid - half, endMs: mid + half }); - // Крен — не точнее одного знака после запятой (как и при ручном вводе). - setRouteRoll(Math.round((selectedShootPass.rollDeg ?? 0) * 10) / 10); + setRouteRoll(clampRoll(selectedShootPass.rollDeg ?? 0)); }, [rightMode, selectedShootPass]); useEffect(() => { @@ -1325,14 +1332,10 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange, }) } onRouteRollChange={(deltaDeg) => - setRouteRoll((current) => - Math.max(-MAX_ROUTE_ROLL_DEG, Math.min(MAX_ROUTE_ROLL_DEG, (current ?? 0) + deltaDeg)) - ) + setRouteRoll((current) => clampRoll((current ?? 0) + deltaDeg)) } onRouteRollSet={(deg) => - setRouteRoll((current) => - Number.isFinite(deg) ? Math.max(-MAX_ROUTE_ROLL_DEG, Math.min(MAX_ROUTE_ROLL_DEG, deg)) : current - ) + setRouteRoll((current) => (Number.isFinite(deg) ? clampRoll(deg) : current)) } onSelectPass={(pass) => { setSelectedShootPass(pass);