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:
@@ -15,6 +15,7 @@ import { readState, writeState } from "../A00_Common/b_page_state";
|
||||
import { noteSnapshotFailed } from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
|
||||
import type { GroupItems, SummaryItem } from "../common_util/common_util_summary_item";
|
||||
import { useCatalogDefinition } from "../common_util/common_util_structure_catalog";
|
||||
import { pipeIdAt } from "../A00_Common/design/design_pipe_ids";
|
||||
import { API_BASE_URL, API_TIMEOUT_MS } from "@config/config_frontend";
|
||||
import {
|
||||
type DefinedRule,
|
||||
@@ -324,7 +325,7 @@ export function pipesToStructureMarks(
|
||||
}>,
|
||||
): StructureInstance[] {
|
||||
return pipes.map((pipe) => ({
|
||||
structure_id: `pipe-${pipe.chainage_m.toFixed(2)}`,
|
||||
structure_id: pipeIdAt(pipe.chainage_m), // 관 세션 id(323-5)
|
||||
type_id: pipe.facility,
|
||||
placement: "point",
|
||||
chainage_m: pipe.chainage_m,
|
||||
|
||||
@@ -382,7 +382,12 @@ export function createCorridorGroup(build: CorridorBuildResult, bounds: ModelBou
|
||||
(build.structures ?? []).forEach((structure, index) => {
|
||||
// 신원표 — 3D 클릭이 이 값으로 부재를 가려낸다(2026-09-04). 이름 규칙은 그대로 둔다
|
||||
// (다른 코드가 이름으로 찾는다). 배관은 겉·속 두 겹이라 자식까지 같은 표를 단다.
|
||||
const tag = { chainageM: structure.chainage_m, kind: structure.kind, key: structure.key };
|
||||
const tag = {
|
||||
chainageM: structure.chainage_m,
|
||||
kind: structure.kind,
|
||||
key: structure.key,
|
||||
structureId: structure.structure_id, // 323-5 — 벽은 id 로 고름
|
||||
};
|
||||
if (structure.kind === "pipe") {
|
||||
const mesh = structurePipeMesh(structure, origin);
|
||||
if (mesh) {
|
||||
|
||||
@@ -86,6 +86,8 @@ export interface CorridorStructure {
|
||||
* 신원표다(2026-09-04). 세월교·BOX암거 부재는 아직 비워 둔다.
|
||||
*/
|
||||
key?: string;
|
||||
/** 구조물 id(323-5) — C군 벽(id 로 고름 · 덮는 측점 어디서 골라도 · 한 번 스윕). 관 부재는 자리로 고름. */
|
||||
structure_id?: string;
|
||||
/** 단면 폴리곤 [offset, elevation][] — 스윕형(벽·집수정). pipe는 없음. */
|
||||
polygon?: Array<[number, number]>;
|
||||
/**
|
||||
@@ -288,6 +290,7 @@ export function buildCorridorStructures(
|
||||
// (2026-08-28 이관 이중그리기 방지 — 횡단도 가드와 같은 규칙).
|
||||
// C군 벽 = 덮는 측점 중 한 곳에서만 · 시작 ~ 끝 스윕(323-4 · 측점마다 통째로 겹치던 것)
|
||||
const wallSweep = wallSweepAt(section, crossSections);
|
||||
const wallId = section.revetment?.structure_id;
|
||||
const revetLayout =
|
||||
wallSweep &&
|
||||
ownWallsDrawAt(section, culvertOwnerFor(section, crossSections)) &&
|
||||
@@ -369,8 +372,10 @@ export function buildCorridorStructures(
|
||||
|
||||
if (revetLayout && wallSweep) {
|
||||
// 다단이면 단마다 솔리드 하나 — 횡단 카드와 같은 폴리곤을 그대로 스윕한다.
|
||||
const from = solids.length;
|
||||
for (const tier of revetLayout.tiers)
|
||||
pushSwept("revet", tier.polygon, wallSweep.beforeM, wallSweep.afterM, null, "own");
|
||||
for (const solid of solids.slice(from)) solid.structure_id = wallId ?? undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,7 +30,7 @@ import type { FlowArrow } from "../B04_PreProcess/B04_PreProcess_UI_FlowArrows";
|
||||
import { buildStrengthArray } from "../B04_PreProcess/B04_PreProcess_UI_FlowRamp";
|
||||
import { resampleRoute } from "../B04_PreProcess/B04_PreProcess_UI_RouteSamples";
|
||||
import { createPipeEditor } from "./B05_Profile_UI_Drainage_Pipes";
|
||||
import { followMovedPipe, PIPE_MATCH_M } from "../A00_Common/design/design_selection";
|
||||
import { followMovedPipe, PIPE_MATCH_M } from "../A00_Common/design/design_pipe_ids";
|
||||
import { snapStructureStation } from "../common_util/common_util_structure_station";
|
||||
import { createFacilityStore } from "./B05_Profile_UI_Drainage_Facility";
|
||||
import { mergeFacilityOptions } from "./B05_Profile_UI_Drainage_Facility_Merge";
|
||||
|
||||
@@ -35,6 +35,7 @@ import { plantsStation } from "./B05_Profile_UI_Profile_Structures";
|
||||
import { listPick } from "../common_util/common_util_structure_catalog";
|
||||
import { planStations } from "../common_util/common_util_structure_station";
|
||||
import { plannedIrregular } from "../A00_Common/design/design_structure_status";
|
||||
import { pipeChainageOf, pipeIdAt, withPipeIds } from "../A00_Common/design/design_pipe_ids";
|
||||
|
||||
/** 관 지점 한 건 — 유효직경·시설 종류·부속 옵션까지 그대로 받는다. */
|
||||
export interface PipeProjection {
|
||||
@@ -119,7 +120,7 @@ export function createStructuresBridge(deps: StructuresBridgeDeps) {
|
||||
if (!planTypes.length) return;
|
||||
const interval = deps.panel().values().stationInterval || 20;
|
||||
applyIrregularStations(
|
||||
plannedIrregular(planStations(planPipes, ownStructures, planTypes), interval),
|
||||
plannedIrregular(planStations(withPipeIds(planPipes), ownStructures, planTypes), interval),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -138,9 +139,7 @@ export function createStructuresBridge(deps: StructuresBridgeDeps) {
|
||||
|
||||
/** 알약 id가 계곡 통과 시설(관 정본)이면 그 누가거리, 아니면 null. */
|
||||
function pipeMarkChainage(structureId: string | null): number | null {
|
||||
if (!structureId?.startsWith("pipe-")) return null;
|
||||
const value = Number(structureId.slice("pipe-".length));
|
||||
return Number.isFinite(value) ? value : null;
|
||||
return structureId ? pipeChainageOf(structureId) : null; // 관 세션 id 장부(323-5)
|
||||
}
|
||||
|
||||
/** 그래프 알약 레인에 올릴 목록 — 구조물 정본 + 계곡 통과 시설(관 정본)을 합친다.
|
||||
@@ -161,7 +160,7 @@ export function createStructuresBridge(deps: StructuresBridgeDeps) {
|
||||
}>,
|
||||
): StructureInstance[] {
|
||||
return pipes.map((pipe) => ({
|
||||
structure_id: `pipe-${pipe.chainage_m.toFixed(2)}`,
|
||||
structure_id: pipeIdAt(pipe.chainage_m), // 관 세션 id(323-5)
|
||||
type_id: pipe.facility,
|
||||
placement: "point",
|
||||
chainage_m: pipe.chainage_m,
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user