feat(구조물표): 323-5 고름 한 칸 — 관 세션 id(옮겨도 그대로 · 저장 안 함) · 고름 강조 한 규칙(pickLights · 카드 형상 · 3D 같은 함수) · 그린 것마다 신원표(data-structure-id + data-part) · 같은 자리 거리 = PIPE_MATCH_M 하나 · 322-0 이 깬 옛 시험 도움 파일 · 줄 수 고침

- `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
This commit is contained in:
2026-10-14 18:06:13 +09:00
co-authored by Claude Opus 5.5
parent c45814f910
commit cb4bab7bf2
37 changed files with 488 additions and 81 deletions
@@ -10,6 +10,8 @@
* ========================================================================== */
import * as THREE from "three";
import { pickLights } from "../A00_Common/design/design_pick_match";
import { isNear } from "../A00_Common/design/design_selection";
/** 3D에서 고른 부재의 신원표 — 메쉬 `userData`에 실려 있는 그 값이다. */
export interface StructurePick {
@@ -17,6 +19,8 @@ export interface StructurePick {
kind: string;
/** 부재키(`inlet`·`outlet`·`extra{i}`·`bextra{i}`·`own`·`basin`·`pipe`). 없을 수 있다. */
key?: string;
/** 구조물 id(323-5 · C군 벽) — 있으면 id 로 맞춤(고름 강조 한 규칙 `pickLights`). */
structureId?: string | null;
}
export interface StructurePickControls {
@@ -64,18 +68,31 @@ export function bindStructurePick(options: {
while (node) {
const data = node.userData as Partial<StructurePick>;
if (typeof data.chainageM === "number") {
return { chainageM: data.chainageM, kind: String(data.kind ?? ""), key: data.key };
return {
chainageM: data.chainageM,
kind: String(data.kind ?? ""),
key: data.key,
structureId: data.structureId,
};
}
node = node.parent;
}
return null;
}
/** 이 부재가 지금 고른 것인가 — 부재키가 없는 선택(측점만)은 그 측점 전부를 켠다. */
/** 이 부재가 지금 고른 것인가 — 고름 강조 한 규칙(카드 형상과 같음 · 부재키가 없는 선택 = 그 자리 전부). */
const asSelection = (pick: StructurePick) => ({
chainageM: pick.chainageM,
structureId: pick.structureId ?? null,
partKey: pick.key,
});
const asTag = (pick: StructurePick) => ({
chainageM: pick.chainageM,
structureId: pick.structureId ?? null,
part: pick.key,
});
function matches(tag: StructurePick): boolean {
if (!selected) return false;
if (Math.abs(tag.chainageM - selected.chainageM) >= 0.01) return false;
return selected.key === undefined || tag.key === selected.key;
return !!selected && pickLights(asSelection(selected), asTag(tag));
}
function applyHighlight(): void {
@@ -138,8 +155,8 @@ export function bindStructurePick(options: {
if (!pick) return;
const same =
selected !== null &&
Math.abs(selected.chainageM - pick.chainageM) < 0.01 &&
selected.key === pick.key;
selected.key === pick.key &&
pickLights(asSelection(selected), asTag(pick));
selected = same ? null : pick;
applyHighlight();
controls.onPick?.(selected);
@@ -162,7 +179,7 @@ export function bindStructurePick(options: {
selectAtChainage(chainageM) {
// 3D에서 부재를 집어 이미 그 측점을 고른 상태면 그대로 둔다 — 되돌아온 동기화가
// 부재 하나짜리 강조를 그 측점 전체로 넓히면 안 된다(2026-09-04).
if (chainageM !== null && selected && Math.abs(selected.chainageM - chainageM) < 0.01) return;
if (chainageM !== null && selected && isNear(selected.chainageM, chainageM)) return;
selected = chainageM === null ? null : { chainageM, kind: "" };
applyHighlight();
},