- `design_pipe_ids.ts` — 장부(세션 pipe-ids) · pipe-s{차례} · 자리 → id(찾는 거리 안 가장 가까운 것) · followMovedPipe 가 id 를 새 자리로(고름 id 그대로 · 순환 import 없게 design_selection 에서 옮김) · 설계 페이지가 프로젝트를 정함(usePipeIds) · 자리 이름 만들기 열 곳 · 되읽기 둘 걷음 · 측점 계획 주인 = 세션 id(withPipeIds)
- `design_pick_match.ts` — pickLights(① id ② 같은 자리 ③ 고른 부분) · paintPicked(카드 통째 형상 · 부분 · 관 계열 · 포장층은 제 손잡이) · tagShape — 관 세트 벽 · 집수정 · 관 · 세월교 측벽 · 몸체 · BOX 끝 · C군 벽 · 물넘이 콘크리트에 신원표 · 형상 판정면은 통째 형상 · 콘크리트만
- 3D — 부재 맞춤 = pickLights(옛 0.01 걷음) · C군 벽 솔리드 = 구조물 id(덮는 측점 어디서 골라도 한 번 스윕한 벽이 켜짐)
- 322-0 이 깬 것 고침 — 노면공 흐름 도움 파일(165 · 231-1) 정의 판 확인 흉내 · design_view_profile 700줄(322-0 이 704)
- 시험: test_323_5_pick(세션 id · 옮김 · 강조 규칙 · 칠하기) · 옛 시험 도움 파일 흉내 · 기대 글 고침
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
161 lines
7.5 KiB
TypeScript
161 lines
7.5 KiB
TypeScript
/* =============================================================================
|
|
* A00_Common/design/design_pick_profile.ts
|
|
* 종단 보기 고름 강조 = 고름 한 칸에서 파생 — 종단도(측점 세로선 · 알약 · 구간 띠 · 233-1) ·
|
|
* 3D(측점 띠 · 핀 · 부재 · 233-2) · 배수유역도(관 마커 · 유역 · 측점 마름모 · 233-3) (조사 AF (나) ② ③ ⑤).
|
|
*
|
|
* 고름 한 칸(`structure-pick`)이 유일 원천이다. 값이 바뀌면(구독) 그 값으로 종단도를 다시 세우고,
|
|
* 관 · 구조물 목록이 바뀐 뒤에도 같은 값으로 다시 얹는다(`paint`) — 화면끼리 손으로 부르던 길이
|
|
* 하나 빠지거나 다른 길이 강조를 끄더라도 종단도는 값을 따라간다. 관 알약 id(`pipe-`+측점)는
|
|
* 그릴 때마다 지금 관 목록에서 다시 찾고 관 id 는 세션 id(323-5)라 관이 옮겨져도 끊기지 않는다.
|
|
* ========================================================================== */
|
|
|
|
import {
|
|
nearestWithin,
|
|
readSelection,
|
|
subscribeSelection,
|
|
type DesignSelection,
|
|
} from "./design_selection";
|
|
import { bindEscapeRelease, ESCAPE_FROM } from "./design_pick_escape";
|
|
import { pipeIdAt } from "./design_pipe_ids";
|
|
import { graphTargetAt } from "../../B05_Profile/B05_Profile_UI_Selection";
|
|
import { irregularStationId } from "../../B05_Profile/B05_Profile_UI_IrregularStations";
|
|
|
|
/** 종단도가 켤 것 — 측점선(알약은 그 측점 짝을 종단도가 맞춤) 또는 알약(구조물 · 관 `pipe-*`). */
|
|
export type ProfilePick = { stationId: string | null } | { structureId: string };
|
|
|
|
export interface ProfilePickSource {
|
|
/** 구조물 측점(수동 + 관 투영) — id 는 `irregularStationId` 를 씌우기 전 값. */
|
|
irregular: () => ReadonlyArray<{ id: string; chainage_m: number; origin?: string }>;
|
|
/** 관 지점 정본(배수유역도). */
|
|
pipes: () => ReadonlyArray<{ chainage_m: number }>;
|
|
/** 일반 측점 카드. */
|
|
sections: () => ReadonlyArray<{ station_id: string; chainage_m: number }>;
|
|
}
|
|
|
|
export interface ProfilePickTarget {
|
|
setSelectedStation: (stationId: string | null) => void;
|
|
setSelectedStructure: (structureId: string | null) => void;
|
|
/** 배수유역도 — 관 마커 · 유역(알림 없이) · 측점 마름모. */
|
|
drainage?: {
|
|
selectPipeAtChainage: (chainageM: number | null, notify?: boolean) => void;
|
|
markStation: (chainageM: number | null) => void;
|
|
} | null;
|
|
}
|
|
|
|
/** 같은 값으로 적힌 측점인가 — 고름 한 칸은 고른 측점의 누가거리를 그대로 적음(찾는 폭 아님). */
|
|
const sameAt = (a: number, b: number): boolean => Math.abs(a - b) < 1e-6;
|
|
|
|
/**
|
|
* 고름 값 → 종단도가 켤 것(B05 규칙). ① 구조물 id = 그 알약(측점선은 제 측점이 있을 때만 · 189-1)
|
|
* ② 그 자리 구조물 측점(같은 m 면 측점 하나 · 구조물이 위 · 272 — 일반 카드 id 는 종단도가 숨긴 측점)
|
|
* ③ 고른 측점 그대로(일반 카드) ④ 그 자리 관 측점 · 관 알약 · 카드(`graphTargetAt` · 188-1).
|
|
*/
|
|
export function profilePickOf(
|
|
selection: DesignSelection | null,
|
|
source: ProfilePickSource,
|
|
): ProfilePick {
|
|
if (!selection || selection.chainageM === null) return { stationId: null };
|
|
if (selection.structureId) return { structureId: selection.structureId };
|
|
const at = selection.chainageM;
|
|
const irregular = source.irregular();
|
|
const exact = irregular.find((entry) => sameAt(entry.chainage_m, at));
|
|
if (exact) return { stationId: irregularStationId(exact.id) };
|
|
const card = source.sections().find((entry) => sameAt(entry.chainage_m, at));
|
|
if (card) return { stationId: card.station_id };
|
|
const own = nearestWithin(
|
|
irregular.filter((entry) => entry.origin === "pipe"),
|
|
at,
|
|
(entry) => entry.chainage_m,
|
|
);
|
|
if (own) return { stationId: irregularStationId(own.id) };
|
|
const target = graphTargetAt(null, at, source.pipes(), source.sections());
|
|
return "pipeChainageM" in target ? { structureId: pipeIdAt(target.pipeChainageM) } : target;
|
|
}
|
|
|
|
export function paintProfilePick(target: ProfilePickTarget, pick: ProfilePick): void {
|
|
if ("structureId" in pick) target.setSelectedStructure(pick.structureId);
|
|
else target.setSelectedStation(pick.stationId);
|
|
}
|
|
|
|
/** 배수유역도 = 그 자리 관 마커 · 유역(없으면 풂) + 측점 마름모 — 재계산이 지운 뒤에도 다시 얹음(a3 · a6). */
|
|
export function paintMapPick(
|
|
map: NonNullable<ProfilePickTarget["drainage"]>,
|
|
selection: DesignSelection | null,
|
|
): void {
|
|
const at = selection?.chainageM ?? null;
|
|
map.selectPipeAtChainage(at, false);
|
|
map.markStation(at);
|
|
}
|
|
|
|
export interface ViewerPickTarget {
|
|
markers: { selectStation: (stationId: string | null, notify?: boolean) => void };
|
|
structurePick: {
|
|
select: (
|
|
pick: { chainageM: number; kind: string; key?: string; structureId?: string | null } | null,
|
|
) => void;
|
|
};
|
|
}
|
|
|
|
/** 고름 값 → 3D 가 켤 것 — 띠(+핀) = 종단 측점선과 같은 측점(구조물 고름은 그 자리 구조물 측점만) ·
|
|
* 부재 = 고른 측점의 부재 전부(부재키가 있으면 그 부재 하나). */
|
|
export function viewerPickOf(
|
|
selection: (DesignSelection & { partKey?: string }) | null,
|
|
source: ProfilePickSource,
|
|
): {
|
|
stationId: string | null;
|
|
member: { chainageM: number; kind: string; key?: string; structureId?: string | null } | null;
|
|
} {
|
|
if (!selection || selection.chainageM === null) return { stationId: null, member: null };
|
|
const at = selection.chainageM;
|
|
const pick = profilePickOf(selection, source);
|
|
const own = selection.structureId
|
|
? nearestWithin(source.irregular(), at, (entry) => entry.chainage_m)
|
|
: undefined;
|
|
const stationId = "stationId" in pick ? pick.stationId : own ? irregularStationId(own.id) : null;
|
|
return {
|
|
stationId,
|
|
member: { chainageM: at, kind: "", key: selection.partKey, structureId: selection.structureId },
|
|
};
|
|
}
|
|
|
|
export function paintViewerPick(
|
|
target: ViewerPickTarget,
|
|
pick: ReturnType<typeof viewerPickOf>,
|
|
): void {
|
|
target.markers.selectStation(pick.stationId, false); // 칠하기만 — 알림이 사슬을 다시 돌리지 않게
|
|
target.structurePick.select(pick.member);
|
|
}
|
|
|
|
/**
|
|
* 종단 보기(종단도 · 3D · 배수유역도)를 고름 한 칸에 묶음 — 값이 바뀔 때마다 그림 · `paint()` = 목록이
|
|
* 바뀐 뒤 · 재계산 뒤 · 폼이 닫힌 뒤 같은 값으로 다시 얹음. 보기가 숨어 있으면(횡단 보기가 종단도를 쥠)
|
|
* 안 그림 — 그 보기가 제 규칙으로 그림 · 보기를 떠나면 구독 끊음. Esc(풀기 한 길)는 좌측 폼까지 닫음.
|
|
*/
|
|
export function bindProfilePick(
|
|
projectId: string | null,
|
|
target: {
|
|
profile: ProfilePickTarget;
|
|
viewer?: ViewerPickTarget;
|
|
form?: { hasSelection: () => boolean; selectById: (id: null) => void };
|
|
},
|
|
host: { visible: () => boolean; onDispose: (fn: () => void) => void },
|
|
source: ProfilePickSource,
|
|
): { paint: () => void } {
|
|
const paint = (): void => {
|
|
if (!host.visible()) return;
|
|
const selection = readSelection(projectId);
|
|
paintProfilePick(target.profile, profilePickOf(selection, source));
|
|
if (target.viewer) paintViewerPick(target.viewer, viewerPickOf(selection, source));
|
|
if (target.profile.drainage) paintMapPick(target.profile.drainage, selection);
|
|
};
|
|
host.onDispose(
|
|
subscribeSelection(projectId, (_selection, from) => {
|
|
paint();
|
|
if (from === ESCAPE_FROM && host.visible() && target.form?.hasSelection())
|
|
target.form.selectById(null);
|
|
}),
|
|
);
|
|
bindEscapeRelease(projectId, host);
|
|
return { paint };
|
|
}
|