chore(조사): 요구 224 정밀 분석 — 325 유토곡선·배수유역 창분리 · 326 측점 스냅 · 327-328 추천 저장·큰 R · 329-330 외부 일위대가 보기 창·근거 맞춤 · 331 CAD 연속 명령(오토캐드 조사) · 332 설정 모달

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6BNBFXFndbqYxu6sZuknK
This commit is contained in:
2026-10-15 12:12:08 +09:00
co-authored by Claude Opus 5.5
parent 837fe3d64a
commit b5b6264016
6 changed files with 317 additions and 0 deletions
@@ -0,0 +1,47 @@
# 325 유토곡선 · 배수유역 창분리 (요구 224-1)
기준 = 837fe3d64. B05 · B06 종단도는 한 벌(`B05_Profile_UI_Profile_Panel.ts:30` · `B06_Section_UI_Section_View_Profile.ts:277` → `sharedProfile`) — 한 번 고치면 두 보기에 같이.
## 지금 — 종단 [창 분할]
- 단추: `A00_Common/design/design_shared.ts:153,158` 빈 button → `toolLead` · `:181` `attachProfilePopout`(`design_profile_popout.ts:25-29`) · 글 「창 분할」/「창 결합」 · 툴팁 `title` · 클래스 `ui-btn ui-btn--ghost b05-route-profile__popout`.
- 열기: `window.open("", "aislo-profile-popout", "popup,width=1400,height=720")`(`:52`) · 막히면 토스트(`:54`) · 스타일 · html 속성 · body 배경 열 때 한 번 베낌(`:61-70`) · 종단 뿌리 DOM 을 새 창 body 로 옮김(`:71-73`).
- 동기화: 따로 없음 — 코드 · 가게 · sessionStorage 는 메인 것 · 같은 함수가 다른 창 DOM 을 그림(BroadcastChannel · postMessage 안 씀) → 고름 · 커서 · 확대 · 스크롤 저절로 맞물림.
- 이벤트: 새 창 keydown/keyup/pointerdown/pointerup/pointercancel · blur 를 메인 document 로 다시 쏨(`:14,76-90`) · 새 창 ResizeObserver → `profile.redraw`(`:93-96`).
- 되돌림: 새 창 pagehide → `slot.append` + 다시 그림(`:31-38,97`) · 메인 pagehide · 페이지 떠남(`design_page.ts:263` → `profile.dispose`) → 새 창 닫고 되돌림(`:39-49`).
- 화면 틀 · 시계 = 요소가 놓인 창 것(`common_util_view_window.ts:11-18` · 230-1/2) · 창 자리 · 크기 기억 없음(매번 1400×720) · 테마는 열 때만 베낌 · CSS `design_page.css:43-56`.
## 종단 안 보조 판 셋
| 판 | 만드는 곳 | 그리는 곳 | 접기 · 크기 |
|---|---|---|---|
| 표 | `design_profile_overlays.ts:61-63` · `_Profile_TableOverlay.ts` | `_Profile_Render.ts:173-199,409-411` | 손잡이 `table-open` · `table-height` |
| 유토곡선 | overlays `:48-59` → `B05_Profile_UI_Profile_MassHaul.ts:190`(손잡이 200-204 · 판 208-249) | `_Profile_Render.ts:415-458` → `MassHaul.draw :360-489` | `masshaul-open`(:220,264-279) · 높이 `masshaul-height`(:224-242) · 범례 `masshaul-visible`(:281-286) |
| 배수유역 | `design_profile.ts:181-199` `drainageSide`(`_Profile_SideTool.ts:11`) → `B05_Profile_UI_Drainage_Side.ts:26` → `_Drainage_Panel.ts:79` · 뼈대 `_Drainage_Chrome.ts:54-149` | 자체 캔버스 `_Drainage_Panel.ts:235-316` | `drainage-collapsed`(:556-567) · 폭 `drainage-width`(Chrome :139-147) |
- 배치: 뿌리 = [「종단」 손잡이][높이 끌기][content](`design_profile.ts:207`) · content = [bodyWrap][배수 판](`:198-199`) · 유토 · 표 판은 bodyWrap 바닥에 absolute 로 겹침(`_Style_MassHaul.css:54-66,168-180`) · 높이 나눔 `_Profile_Heights.ts:66-179` · 유토는 종단 본문 크기 0 이면 건너뜀(`_Profile_Render.ts:125`).
## 종단 그래프 좌측 상단 단추 줄
- DOM: `.b05-route-profile__balance`(28px · `_Style.css:533-547`) 맨 앞 `span.b05-route-profile__tools` 안 [창 분할] + `button.b05-route-profile__tool` 8개(초기화 ↶ ↷ 직선화 자유지정 쉬프트 ▲ ▼).
- 만드는 코드: `tools.render()`(`_Profile_Tools.ts:161-232` · 단추 함수 69-80) → `withToolLead`(`design_profile.ts:296-299`) → `renderBalanceBar`(`_Profile_Balance.ts:48-51`) · 그릴 때마다 새로.
- 양식: 글자 단추 · `title` 툴팁 · `--btn-bar-padding` · `--btn-bar-font-size`(`ui_template_theme.css:169-170` · 높이 16 · 글 12) · [창 분할] = ghost + `design_page.css:53-56`.
## 뗄 때 손볼 곳
1. 높이 나눔 · 끌기가 「`overlay.hidden` 거짓 = 열림」만 봄(`_Profile_Heights.ts:70` · `_Profile_Drag.ts:146,172,194`) → 떼어도 메인이 자리 비움 · 높이 끌기 놓으면 새 창 높이가 저장 높이로 굳음 → 「열림 && 뿌리 안」.
2. 유토 손잡이가 메인에 남음 → 뗀 동안 숨김.
3. 「같이 줄이기」 인라인 높이(`_Profile_Heights.ts:147`)가 새 창에 남음 → 뗄 때 비움.
4. 유토 세로 범위가 메인 보이는 구간을 따름(`_Profile_Render.ts:441,453-458`) → 새 창이 넓으면 잘림 → 뗀 동안 `viewRange` 안 씀(`MassHaul.ts:441`).
5. 배수 지도 크기 관찰 = 메인 창 ResizeObserver(`_Drainage_Parts.ts:468`) → 놓인 창 것으로 바꿔 끼우는 `rehome`(직전 크기 지켜 배율 유지).
6. 방향키 ←→ 는 마지막 누름이 `.b05-route-profile` 안일 때만(`B06_Section_UI_Cross_Panel_Base.ts:92`) → 선택자에 두 판 클래스.
7. 종단 새 창 안에서 다시 떼면 메인 `window.open` 이 팝업 차단될 수 있음 → 단추가 놓인 창으로 엶.
8. 창 이름은 조각마다 달리(`aislo-masshaul-popout` · `aislo-drainage-popout`) — 같으면 앞 창 body 를 덮어 조각이 사라짐.
9. 한계: 메인 종단 패널 접으면 뗀 유토 멈춤 · 유토 창으로 메인을 다 가리면 메인 화면 틀이 멈춰 늦음 · 토스트 · 좌측 폼은 메인 창.
700줄: `design_profile.ts` 700(시험 고정) · `design_view_profile.ts` 700 → 손대지 않음 · `_Drainage_Panel.ts` 700 → 한 줄 더하면 주석 합쳐 700. 다른 224 일감과 겹치는 파일 없음.
## 시험
- 있음: `test_282_1_profile_toolbar.py`(+ helper · 가짜 require 가 `showToast` 만 → popout 이 `viewOf` 를 가져오면 helper 도 고치거나 `button.ownerDocument?.defaultView ?? window`) · `test_187_1_b05_head_button_size.py`(`.b05-route-profile__tools > .b05-route-profile__popout {` 블록 정규식 → 새 단추 규칙은 따로) · `test_230_1_popout_wheel.py` · `test_230_2_popout_frames.py`(`POPOUT_FILES` 메인 창 rAF/setTimeout 금지) · `test_219_1_arrow_owner.py` · `test_design_profile_single.py` · `test_b05_drainage_side.py`(배수 파일은 `UI_Profile_` · `B06_Section` import 금지 · ≤700) · `test_281_1_drainage_zoom_range.py`.
- 새로: `test_224_1a_drainage_rehome.py`(+ cjs) · `test_224_1b_masshaul_docked.py`(+ cjs) · `test_224_1c_subpanel_popout.py`(+ cjs · 단추 자리 · 글 · 클래스 · 창 이름 셋 다름 · 열림/닫힘/dispose 제자리 · 손잡이 숨김/보임).
@@ -0,0 +1,35 @@
# 326 계획노선 편집 — 측점 스냅 (요구 224-2)
기준 = 837fe3d64.
## 해석
- 「호의 사이」 = BC~EC 누가거리 구간(서버는 곡선표 BC · EC 를 노선에 내려 구함 `B05_Profile/B05_Profile_Engine_Sections_Core.py:241-275` · 5 m 밖 곡선 버림 `:221`).
- 「호의 중심」 = 원 중심(노선 밖)이 아니라 곡선 중점 MC(IP 에서 내각 2등분선으로 외할장 SL · 누가거리로 BC~EC 한가운데 · `resources/knowledge/original/산림과임업기술(임도)/2. 임도/5. 설계.md:191`).
- 근거: 곡선부 BC · MC · EC 보조측점(`…/4. 노선측량.md:144`) · 「측점간의 번호는 20m이내에서 조정」(`:143`) · 교재 예시 표(`5. 설계.md:182-191`).
- 후보: (가) 호 안 20 m 측점을 MC 로 옮김 — 채택 · (나) 편집 손잡이를 IP 대신 MC 에(「측점」 말과 안 맞음) · (다) 노선을 움직여 MC 를 격자에(곡선 수십 곳 동시 불가).
- 실제 어긋남(저장소 자료 셈): 1078 m · 곡선 26 · R 12~199 → 호 안 측점 14곳 · MC 와 최대 11.3 m(호 616.1~646.5 · MC 631.3 에 620 · 640 둘) · 1153 m · 곡선 50(R 12 · CL ≤ 5.6) → 호 안 11곳(2.7 m 안) · 측점 없는 호 39곳. 측점 = 노선 모양과 상관없는 20 m 격자라 어떤 조작이든 같음.
## 지금
- 편집 창: `A00_Common/design/design_view_profile.ts:278-286` → `openRouteEditModal`(`B05_Profile/B05_Profile_UI_RouteEdit.ts:104-110`) · 단추 줄 `_RouteEdit_Chrome.ts:51-78`([노선 추천] | 분석 지형 · [1m 등고선] · [거리 재기] | 되돌리기 · 다시하기 · 초기화 · 취소 · 확인) · 켬끔 본보기 = [1m 등고선](`aria-pressed` + `is-active` `:63-65` · 배선 `_RouteEdit_Contour.ts:136-148`).
- 조작: 노드 끌기 `_RouteEdit_Pointer.ts:265-294` · 곡선 손잡이 BC · EC `:241-263` + `_RouteEdit_Curve.ts:89-109` · R · 곡선 길이 · 고정 `_RouteEdit_CurveBar.ts:61-116` · 두 번 = 노드 더함 · 오른쪽 = 지움(`RouteEdit.ts:494-512`) · 측점 눈금 누름 = 횡단 미리보기(`_Pointer.ts:186-201`) · 추천 [적용] `_RouteEdit_Initial.ts:132-160` → `RouteEdit.ts:428-434` · 기본 노선 `_ensure_planned_initial`(`B05_Profile_Router_Replan.py:339-379`) · [초기화] `_Initial.ts:164-197` · `Router_Replan.py:282-297`.
- 계산: 편집 중 TS 짝 `buildEditedPolyline`(`_Curve.ts:226-327`) · [확인] 서버 `replan`(`Router_Replan.py:580-669`) 전 체인.
- 곡선 자료: apex · radius_m · tangent_m · inner_angle_deg · start(BC) · end(EC)(서버 `common_util/common_util_route_polyline.py:104-141` · TS `_Curve.ts:32-42`) · 원 중심 셈만(`:130-152`) · MC 셈 코드 없음.
- 측점 정본 `generate_sections`: 20 m 격자 `_chainages`(`_Sections_Core.py:86-92,333`) · 관 · 구조물 0.005 m 안이면 격자에 얹음(`:338-357`) · 종류 bp/ep/irregular/regular(`:411-424`) · 곡선표는 확폭에만(`:368-385`) · BC · MC · EC 측점 안 심음 · 부르는 곳 둘 = `run_section_generation`(`B05_Profile_Engine_Sections.py:392-438`) · `sample_structure_sections`(`:513-551`) · 곡선표 읽기 `_load_design_curves`(`:71-96`).
- 편집 창 눈금: `drawStationTicks`(`B04_PreProcess_UI_MapOverlays.ts:225-260`) ← `_RouteEdit_Render.ts:461-482` · 같은 격자 따로 셈 = 누름 `_Input.ts:368-407` · 기울기 라벨 `_Slope.ts:54-60` · 종단 띠 `_ProfileStrip.ts:26-60` · 횡단 미리보기 서버 = 가장 가까운 격자(`Router_Terrain.py:172-184`).
- 측점 한 벌(272): `plantedChainages`(`common_util_structure_station.ts:235-242`) ↔ `resolve_extra_stations`(`Engine_Sections.py:250-275`) · `oneStationList`(`ts:254-274`) ↔ `_merge_irregular_into_longitudinal`(`Router_Confirm.py:295-343`) · `followMovedNodes`(`ts:276-298`) ↔ `_follow_moved_stations`(`Router_Confirm.py:204-292`) · 거울 시험 `test_272_1_station_list.py` · 정수 m = 구조물 · 관만 floor(x+0.5)(`common_util_structure_station.py:38-45` · `.ts:51-60`).
## 구현 자리 (짝 ① · 조작 중 TS · [확인] 서버 정본)
0. 쪼개기: `Router_Replan.py` 698줄 → 요청 모델 다섯(`:72-138`)을 `B05_Profile_Schema.py` 로(Replan 에서 다시 import · `replan.RouteInitialRequest` 그대로).
1. 짝 셈: `_Sections_Core.py` `snap_curve_stations(stations, spans)`(호 안 BC+0.005 < s < EC−0.005 측점 중 MC 에 가장 가까운 하나 → floor(MC+0.5) · 같은 m 하나로) · `generate_sections(..., station_snap=False)`(spans 셈을 앞으로 · 격자 붙임은 스냅 뒤 목록) ↔ `_RouteEdit_Frame.ts`(77줄 · Sections_Core 짝 · `snapCurveStations` · `curveSpans` · `stationsAlong` · import 없이).
2. 저장: 곡선표 `planned_route_curves.json` 에 `"station_snap"`([확인] 때만 · 작업본 · 스냅샷이 route 폴더째 복사 `common_util_initial_snapshot.py:39`) · 없으면 끔 · DB 옵션에 안 둠(`SectionGenerationOptions` 다시 짓는 곳 여섯) · `Router_Replan.py` 요청 칸 → `_write_planned_polyline`(`:300-336`) · GET `/route/plan` 돌려줌(`:558-577`) · 실패 되돌림(`:607-639`)에 `_curves.json` · `_nodes.csv` 도(기존 틈) · `Engine_Sections.py` `load_station_snap` → `run_section_generation` · `sample_structure_sections` 둘 다(한쪽만이면 [저장] 때 640 되살아남) · `Router_Terrain.py` 횡단 미리보기 `station_snap` + `design_curves=built.curves`.
3. 편집 창: `_RouteEdit_Chrome.ts` [측점 스냅](1m 등고선 양식 · [거리 재기] 옆) · `RouteEdit.ts`(635줄) 상태 = 메모리 모드(되돌리기 밖) · 열 때 GET 값 · `stations()` 한 곳 → 그리기 · 누름 · 기울기 · 횡단 요청 · [확인] `changed` + `station_snap` · 함께 `_RouteEdit_Render.ts` · `B04_PreProcess_UI_MapOverlays.ts`(`chainages` 선택 칸) · `_Input.ts`(`stationAtScreen` 선택 칸 · 라벨 판정 값으로) · `_Pointer.ts` · `_Slope.ts` · `_Apply.ts` · `B05_Profile_Api_Replan.ts` · 선택 칸은 뒤에(B04 지도 · 배수유역도 · `helper_b05_routeedit_pick.cjs` 그대로).
영향: B05 종단 · 3D · B06 · B07(`Router_Support_Basin.py:280-290`) · B08 = 서버 측점 목록 따라감 · 옮긴 측점의 B06 사용자 입력은 누가거리 정확 맞춤 이월이라 안 넘어감(`B03_FileInput_Service_Chain.py:574-586`) · 추천 비교값 · 종단 띠 균형 종단 = 20 m 격자 그대로 · B04 지도 · 배수유역도 눈금 격자 그대로(`_Drainage_Panel.ts` 700줄) · `common_util_route_polyline.py`(745줄) 손대지 않음.
## 시험
- 새로: `test_224_2_station_snap.py`(tsc 컴파일해 파이썬과 견줌 · 곡선 없음 · 측점 없는 호 · 하나 · 둘 · BC 위 · .5 올림 · 이웃 격자 같은 m · `generate_sections` 끔 = 지금과 같음 · 켬 = 640 없음 · 631 있음 · 631 에 관이면 `grid`).
- 있음: `test_74_route_confirm_initial` · `test_route_initial` · `test_64_1_profile_toggle` · `test_b05_routeedit_frame_mirror` · `test_b05_routeedit_pick` · `test_b05_routeedit_chrome_modal` · `test_b05_routeedit_cross_width` · `test_272_1_station_list` · `test_272_2_station_sync` · `test_238_1_save_keeps_structure_stations` · `test_273_2_preview_stations` · `test_b05_extra_stations_supplier` · `test_regeneration_extra_stations` · `test_b05_station_roundtrip` · `test_b06_curve_widening` · `test_75_profile_strip`.
@@ -0,0 +1,53 @@
# 327 · 328 계획노선 추천 — 영구 저장 · 큰 R (요구 224-3 · 224-4)
기준 = 837fe3d64. 경로는 저장소 뿌리 기준.
## 지금 — 추천 계산 길
- 화면: 편집 창 [노선 추천] → 모달 `B05_Profile/B05_Profile_UI_RouteEdit_Initial.ts:132-162` · 알약 다섯(예상노선 추적 · 영선 · 토공 최소 · 기울기 최소 · 비중 · `_Recommend.ts:66-74`) · 열면 지금 갈래 한 번 셈 `:552-554` · 알약 = `run() :423-480` · 서버 갈래 여덟 중 safe · crossings · cost 숨김(`_Initial_Model.ts:439`) · [적용] = 편집값 + sessionStorage `routeedit-initial`(`_Initial.ts:151-159` · `_Initial_Model.ts:561-564`) · 서버 안 씀 · 편집 창 [확인] → `POST /route/replan`(`_RouteEdit_Apply.ts:86-101`) → 작업본 `planned_route.csv` · `_nodes.csv` · `_curves.json` + `B05_Profile/route/initial_choice.json`(갈래 이름) + initial_snapshot 다시 뜸(`B05_Profile_Router_Replan.py:607-663`).
- API: `POST /api/projects/{id}/route/initial`(`B05_Profile_Router_RouteInitial.py:27-126` · 화면 대기 5분 `B05_Profile_Api_RouteInitial.ts:15`) · 입력 = 예상노선 `B05_Profile/route/expected_route.csv`(없으면 initial_snapshot) `:59-61` · 최소 R `_plan_criteria :67` · 종단 상한 `:68-70` · 제약 칸(기울기 · 최소 R · 계류 이격 100 · 지반 고름 5 · 영선 목표) `:72-79` · 지형(확정 지표면 + 분석 지형) `:80-83` → 비용면 캐시 `B05_Profile_Engine_Solver.py:214-257` · 계류 `도엽_하천중심선.geojson` 소하천 이상(`Engine_RouteInitial.py:87-137`) · 관 지점 · mode · weights(50/25/25) · with_profile · design_speed.
- 차례(`generate_initial_route` `Engine_RouteInitial.py:212-350`): ① 예상노선 추적 = 전처리 초기값 규칙(0.4 초) ② 나머지 = 후보 묶음(입력 지문 `_pool_key :361-372` · 메모리 4벌 `_POOLS :108-110`) — 격자 탐색(예상노선 ±100 m 복도 · 4 m 칸 · 16방향 Dijkstra · `Search.py:236-301`) · 비용(이격 · 토공 · 기울기² · 영선 · 위험 · 계류 · 길이 · 꺾임 R·θ) · 후보(목표 7 × 상한 3단 + 쓸기 11 · `Pool.py:50-213`) · `_trace Pool.py:99-114` → `route_metrics Metrics.py:115-293` · 균형 종단(후보 전부 대충 → 고른 하나 바로 · ±60 m 횡단 · 계획고 ±6 m · B06 `compute_cross_design` · `Profile.py:66-402`).
- 1분 까닭: 같은 입력 첫 셈 35~80 초 · 같은 묶음 다른 갈래 3~5 초 · 묶음 짜기 1.9~2.7 초 · 대부분 = 후보 전부 균형 종단(63-4 때 1.8 → 18.3 초) · 메모리만이라 재시작 · 리로드 · 입력 바뀜마다 다시.
- 저장 없음: 남는 것 = 메모리 `_POOLS` · sessionStorage 한 벌 · `initial_choice.json`. 머리글 「파일 안 씀」 = `Engine_RouteInitial.py:6` · `Router_RouteInitial.py:33` · `Router_Replan.py:29` · `Profile.py:4-5`.
## 327 저장 설계
- 키: 시점 · 종점만으론 부족(추천은 예상노선 전체 씀) · 기존 `_pool_key`(지형 · 예상노선 · 계류 · 상한 · 최소 R · 이격 · 고름 · 목표 기울기 내용 해시) + mode · weights · with_profile · design_speed · `ALGO_VERSION`(코드 판 · 선례 `B04_PreProcess_Router_Watershed.py:82-87,227-242` `RESPONSE_SCHEMA_VERSION`) · 관 지점은 뺌([확인] 때마다 다시 냄 · 덮개 수는 꺼낼 때 다시 셈 `Profile.py:432-451`).
| 자리 | 판단 |
|---|---|
| `B05_Profile/route_recommend/<키>.json` | 씀 — initial_snapshot 대상(`common_util_initial_snapshot.py:38-43`) 밖이라 [초기화](`:154-159,260-274`)에 안 휩쓸림 · 자료 교체(`common_util_project_reset.py:23-54`) · 프로젝트 삭제 때 같이 지워짐 |
| `B05_Profile/route/` | 안 됨 — [초기화]가 되돌림 |
| initial_snapshot | 안 됨 — 쓰는 때 정해짐 · [확인]마다 통째 지움(`Router_RouteInitial.py:284` · `B03_FileInput_Service_Chain.py:449`) |
| DB 표 | 안 씀 — 마이그레이션 · 정리 목록까지 |
- 층 = 「서버 계산 캐시」(데이터 3층 밖 · 선례 비용면 npz `Solver.py:169-257` · 코리도 사전 생성 · 배수유역 응답) · 사용자 편집값 아님 → 자동저장 금지와 안 부딪힘.
- 꼴: 키마다 파일 하나 `{version, key, pool, default, created_at, request, result}` · 있으면 셈 안 함 · 없으면 셈해 더함(덮어쓰기 없음) · `default` = 빈 제약 칸 + 50/25/25 + 설계 지형 · 이 표시는 안 지움 · 한 벌 10~30 KB.
- 무효화: 지형 · 예상노선 · 계류 · 설정 바뀜 = 다른 키(옛 파일 남김) · 코드 바뀜 = `ALGO_VERSION` 올림 · 자료 교체 = 폴더째.
- 미리 셈: 체인 끝(`Service_Chain.py:225-227` 예상노선 · `:343-351` 스냅샷 뒤)에서 예약만(`_schedule_background_task` `B03_FileInput_Router_Helpers.py:279-299` · 체인이 [초기화] 요청 안에서도 불림 `B05_Profile_Router_Lifecycle.py:246`) · 다섯 알약 ≈ CPU 1~1.5 분 · 진행 중 같은 키 = 기다림.
## 328 큰 R
- R 정하는 자리: `common_util/common_util_route_polyline.py:670-717`(접선 자리 = 앞뒤 직선 짧은 쪽 절반 `:673` · 기본 최소 `:682` · 못박은 R `:683-685` · R 맞춤 `_fit_radius_m :358-391,686-697` = 최소 이상에서 예정노선과 평균 거리 최소 · 단순화 갈래에서만 · 같은 방향 꺾임 묶기 `:449-576`) · 추천은 예상노선 추적만 맞춤 · 나머지 `_trace` = 편집 갈래 → R = 최소 → **추천 곡선 전부 12 m**(자리 모자라면 더 작게 + 위반 `:698-716`).
- 12 m 출처: 설계속도 20 · 특수지형 `config/config_system_design.py:521-525` ← `B05_Profile_Engine_Grade.py:142-151` ← `Router_Replan.py:188-226`.
- 제약: 이웃 접선 겹침(절반씩) · 탐색 경로 이탈(외할 E = R(sec(IA/2)−1)) · 종단경사 · 계류(맞춤 뒤 다시 잼 · 위반 늘면 안 됨) · 토공(R 크면 확폭 줄어 토공 줄음 `Profile.py:127-151`) · 곡선 길이 하한(법정 없음 · 표시 5 m `config_system_design.py:548-557`).
- 방안 A(1단 · 「현재 노선 로직」 재사용): `_trace` 에서 탐색 경로를 예정노선처럼 폴리라인화 `build_planned_polyline(densify_route(nodes, 2 m), min_radius_m)` → 같은 단순화 · 묶음 · R 맞춤 · 최소 R 판보다 위반 많으면 최소 R 판 · 응답 꼴 같아 화면 `flattenServerPlan`(`_RouteEdit_Edits.ts:232-272`) 그대로 · +5~12 초.
- 방안 B(2단 · 「클수록」): A 곡선마다 R 을 [맞춘 R, 접선 자리 ÷ tan(IA/2)] 안에서 탐색 경로와 최대 거리 ≤ 허용 이탈인 가장 큰 값 → 못박아(`simplify=False · radii`) 다시 짬(브라우저 짝 있음) · +1~3 초 · 허용 이탈 값은 사용자 확정.
- 방안 C(탐색 자체 큰 R): 층 수 ∝ R · 1.5~2.5배 느림 · 안 씀.
- 붙일 곳 `_trace` 한 곳 → 영선 · 토공 최소 · 기울기 최소 · 비중(+숨긴 셋) · 예상노선 추적은 그대로(`test_route_initial.py:102` · `Router_Replan.py:300-336` · 09-07 확정 「예정노선에 가장 가까운 R」).
- 손대지 않음: `common_util_route_polyline.py`(745줄 · TS 짝) · `Router_Replan.py`(698줄). 설명 정정: `B05_Profile_UI_RouteEdit_Recommend_Help.ts:32` 추적 「최소 R」 → 「맞춘 R」.
## 지식DB 근거 (값은 안 정함)
- 법정 최소곡선반지름(별표2 Ⅰ.2.다): 설계속도 40 → 60/40 · 30 → 30/20 · 20 → 15/12(일반/특수) · 중심선 기준 · 내각 155° 이상 곡선 생략 · 배향곡선 10 m — `resources/knowledge/original/법률/산림자원의 조성 및 관리에 관한 법률 시행규칙/별표/별표2_…(제5조제1항 관련).md:151-165`(산불진화임도 `:436-450`).
- 정리본 `resources/knowledge/technical_info/01_임도/02_상세설계/평면선형.md:17-72`(작업임도 규정 없음 · 확폭 · 45 m 이상 확폭 불요 · 곡선반지름 확보 우선).
- 「곡선반지름은 … 가급적 큰 것이 좋음」 — `original/임도기술교본/1. 교본의 개요/3. 용어해설.md:95` · 확보 우선 `3. 임도 계획과 설계/3. 실시설계.md:229,273`.
- 산식 · 표 `original/산림과임업기술(임도)/2. 임도/2. 임도구조.md:65-106`(R = ℓ²/4B · 20 m 목재 · 노폭 4 m → 25 m · R = V²/127(i+f) · 20 km/h 14~16 · 30 → 31~35 · 40 → 55~63) · 곡선부 종단 G − 80/R > S `:221-226` · K = R/I ≥ 3.0 `:235-248` · 예각 내각은 R 크게 못 둠 `:250`.
- 25 m 이하 = 25톤 대형차 곤란 · 구조개량 대상 — `original/행정규칙/임도설치 및 관리 등에 관한 규정/별표/별표3_임도구조 개량사업(제17조 관련).md:25,55,77`.
- 곡선 요소 식(TL · CL · SL · M · 접선 겹침) — `technical_info/01_임도/03_계산정보/곡선설치_계산.md:19-34`.
## 시험
- 추천: `test_route_initial.py` · `test_63_4_route_profile.py` · `test_77_recommend_widening.py` · `test_78_recommend_mass.py` · 화면 `test_b05_routeedit_initial.py`(+ cjs) · `test_b05_routeedit_initial_roundtrip.py`(+ cjs).
- R: `test_route_polyline*.py`(curve_fit · edit · apex_roundtrip · idempotent · handle_drag) · `test_user_values_survive.py` · `test_plan_curve_limits.py` · `test_b05_curve_radius_floor.py` · `test_initial_snapshot.py`.
- 새로: `test_224_3_recommend_store.py`(같은 입력 같은 키 · 하나만 바뀌어도 다른 키 · 두 번째 셈 0회 · 덮어쓰기 없음 · 스냅샷 복원 뒤 남음) · `test_224_4_recommend_radius.py`(R ≥ 최소 · 하나 이상 큼 · 위반 ≤ 최소 R 판 · 추적 그대로 · (B) 이탈 ≤ 허용).
@@ -0,0 +1,52 @@
# 329 · 330 외부 일위대가 — 엑셀 보기 창 · 단가산출 근거 맞춤 (요구 224-5 · 224-6)
기준 = 9edaec82e(324 끝). M02 = `M02_MasterTemplete/`.
## 지금
- 올리기: `Hopyo_Side.ts:38-72` → Router `:201-216` → `_upload :174-179` → `Excel.read_book` → `Store.save :72-87` — 읽은 결과만 `실무자료/HP-######.json`(원본 엑셀 안 남김).
- 본 화면: `Hopyo_View.ts:132-186` 호표 표 → `:207-268` 줄 표(11열) · 모달 = `choose() :359-394`(후보 5 + 다른 로직 찾기) 하나.
- 불러오기 길 둘 · 둘 다 호표 하나씩: ① build `:414-425` → `POST hopyo-build`(Router `:291-302` → `Build.py:191-204`) ② [로직 추가] `Labor_Detail.ts:496-512` → `Hopyo_Pick.ts:18-58`.
- 서버가 읽는 것: `Excel.py:69-97` 「일위대가」·「일위대가총괄표」 값만(xls formatting_info 없음 · xlsx read_only) → 호표 `{번, 이름, 규격, 단위, 금액}` · 줄 `{차례, 이름, 규격, 수량, 단위, 단가, 금액, 비고, 참조, 갈래}` · 검산. 격자 · 병합 · 열 폭 · 서식 · 행 번호 · 단가산출 시트 없음.
- xlrd `formatting_info=True`(북부청 .xls 0.05 초): 병합 11 · 열 폭 15열 · 행 높이 · 숫자 형식 `#,##0` · 가로 맞춤 · 테두리 · 굵게/채움 없음. xlsx 병합 · 폭은 openpyxl 비 read_only(무거움) → xlsx 는 값만.
- 크기: 북부청 일위대가 560행×15열(≈129 KB) · 단가산출 4,345행×6열(≈192 KB) · 거창 xlsx 단가산출 146열. 호표 행 범위 깔끔(제3호표 = 20–24행 · 68개 안 겹침).
## 329 엑셀 보기 창 — 부품
| 후보 | 근거 | 판단 |
|---|---|---|
| A00 산출근거 스프레드시트 `createSpreadsheet(host, book, {readOnly, tabs, onSelect})` | `A00_Common/spreadsheet/spreadsheet.ts:56-115` · Workbook `spreadsheet_types.ts:165-242`(병합 · 열 폭 · 행 높이 · 서식 · 틀고정 · 여러 시트) · M02 모달에서 읽기 전용 사용 중 `AreaCellPick.ts:49-118` | **씀** — 엑셀 모양 · 보이는 칸만 그림 · 늦게 불러옴 · 손잡이에 `select(범위)` 하나 더함(안쪽 `select :250-266` · `selectSpan` `spreadsheet_selection.ts:89` 있음) |
| B08·B09 자료 표 `ui_template_data_table.ts` | focusRow · rowClass | 병합 · 열 글자 없음 |
| 집계표 `createSheet` | 양식 문서용 | 안 맞음 |
## 330 근거 맞춤 — 자료
- 우리: 로직 1,469(돈 1,438) · 칸 = 키 · 원문번호 · 구분(산림품셈 · 건설품셈 공통/토목/건축/기계설비/유지관리) · 출처(「건설품셈 공통 3-2-1」) · 호표 줄 `{인력/재료/기계/로직, 요소 LB…, 단위, 수량식, 비목}` · 조항 묶음 `M02_Labor_Inputs.py:68-85` sources() · 인력 LB 378 · 기계 EQ 654.
- 엑셀: 일위대가 줄 비고 「단산N참조」 → 단가산출 B열 머리 「N.이름[단위]」(북부청 268/268 · 사방 139/139 이어짐) · 블록 안 출처 글(「산림사업표준품셈 9-14…」 「[건설표준품셈(3-1-3)]」 · 오타 「건설표줌품셈」) · 직종 줄(「보통인부 : 172,068 × 0.1인」 · 글자 사이 빈칸) · 출처 있는 블록 북부청 90/95 · 사방 24/67 · 직종 있는 블록 69/95 · 48/67.
- 지금 점수(`Match.py:297-325`): 두 글자 겹침 + 이름 같음 1 + 단위 0.15 + 산림 0.05 + 자재 규격 0.5 · 거름 `:307` 때문에 이름이 아주 다르면 후보에도 못 듦 · `_targets :215-256` 에 조항 · 직종 없음 · 북부청(기억 없음) 단가산출 268 = 같음 63 · 후보 200 · 없음 5.
| 단산 | 엑셀 이름 · 근거 | 지금 1순위 | 근거 1순위 |
|---|---|---|---|
| 93 | 구조물터파기(인력100%)(토사) · 건설 3-2-1 · 특별인부 | 구조물터파기 육상토사 GF000101 | GC000083 굴착(인력/토사) |
| 21·22·23 | 레미콘타설 · 건설 6-1-1 · 콘크리트공+보통인부 | 펌프카 타설 | GC000198 레디믹스트콘크리트 타설 |
| 64 | 비탈면보호공(평떼) · 건설 4-1-1 · 조경공+보통인부 | 비탈면 보양 | GC000143 잔디붙임 |
| 49 | 흙깍기(토사)(굴삭기1.0㎥) · 건설 3-2-3 | 없음(깍/깎) | GC000085 흙깎기(기계) 보통토사 |
함정: ① 건설품셈 번호에 부문 없음(잔토처리 3-1-3 ↔ 우리 유지관리 철골재 철거) → 공통 · 토목 덤 · 직종 · 낱말 하나도 안 겹치면 버림 ② 품셈 판 다름(산림 5-23-3 ↔ 우리 5-22-3) → 조항 같음은 덤 점수만 · 자동 확정 안 씀 ③ 보통인부는 겹침에서 뺌 ④ 직종 없는 기계 로직은 부문 덤이 가름.
## 위험 · 지킬 것
- 줄 수: Match 530 · View 485 · Router 312 · Excel 269 · Build 204 · Api 190 · Side 128 · Pick 58 · spreadsheet.ts 363 · `Labor_Detail.ts` 693(시험 고정 · 손대지 않음 · 덧줄은 Hopyo_Pick).
- 원본 읽기만: `resources/master_data/*.json` · `일위대가조합.json`(시험 master_hash).
- 길 충돌: `/hopyo/{key}/{no:int}`(Router `:228`) → 격자 길은 `/hopyo-grid/{key}`.
- `Match.py:464` 요약 `h[k]` → 옛 파일 KeyError → `.get`.
- 격자는 따로 파일 `실무자료/격자/<키>.json`(HP json 에 넣으면 목록 · 고르기가 통째로 읽음) · 지우기 때 같이.
- 이미 올린 파일 = 격자 · 근거 없음 → 다시 올려야 보임.
- `A00 spreadsheet.ts` 공용(고쳐도 됨) · 계약 `spreadsheet_types.ts` 안 고침.
- 기대값 바뀜: `test_m02_hopyo_match.py:106-108` 제3호표 구조물터파기 1순위 → 굴착(인력/토사)(의도).
- STmate 꼴(「일위대가표」·「단가산출근거」 · 실무 xlsx 6개)은 지금 「시트 없음」 400 — 이번 밖.
## 시험
- 있음: `test_m02_hopyo_excel.py` · `_match.py` · `_build.py` · `_screen.py`(+ `helper_m02_hopyo_pick.cjs`) · `spreadsheet/test_spreadsheet_d_input.py`(+ `helper_spreadsheet_d.cjs`).
- 새로: `test_m02_hopyo_grid.py`(병합 11 · 15열 · B20 제3호표 · `#,##0` · 행 범위 68 안 겹침 · [20,24] · 200/404 · 지우면 격자도 · 역할 · master_hash) · `test_m02_hopyo_basis.py`(블록 북부청 95 · 사방 67 · 단산93 → GC000083 · 21 → GC000198 · 64 → GC000143 위 둘 · 49 → GC000085 · 잔토처리에 철골재 철거 없음 · 근거 없는 줄 그대로 · master_hash).
@@ -0,0 +1,66 @@
# 331 내장 CAD 키보드 연속 명령 (요구 224-7)
기준 = 837fe3d64. 경로 `B07_DesignDetail/openwebcad/` 아래 `src/…`.
## 지금 — 우리 코드
- 이미 됨: `L␣` → 첫 점 → `@100<30⏎`(한 줄 극좌표) · 마우스 방향 + `100⏎`(직접 거리).
- 안 됨: `<30⏎` → `100⏎`(각도 고정 뒤 길이 · `<30` 읽는 곳 없음 · 버려짐) · 이동 · 복사 · 회전은 클릭만.
- 버그: 시퀀스 도구(폴리선 등) 첫 점에 숫자 · `@x,y` → 기준점 없어 오류 → 「Error in tool actor」 · 도구 멈춤(`sequence-tool.ts:183-189,284-285` · `get-point-from-event.ts:26-28,42-44` · `state.ts:309-317`).
| 길 | 자리 |
|---|---|
| 키 | `src/inputController/input-controller.ts:92` keydown → `handleKeyStroke :340-448` · 글자 대문자로 `this.text` 에 쌓음 `:441-447` |
| 커서 옆 칸 | `draw :127-142` → `input-field-draw.ts:37-86`(안내 · 절대 x,y · 친 글 · 후보 6) |
| 명령줄 | `components/CommandLine.tsx:17-39`(↑↓ 이력 · Tab 완성 `:78-81`) · 기본 숨김 `components/Toolbar.tsx:23` |
| 단추 | `ribbon/Ribbon.tsx:172` · `components/QuickAccessBar.tsx:33` → `commands/run-command.ts:26-53` · 손가락 `input-touch.ts:122-140` |
| 이름 · 별칭 | `commands/commands.{draw,modify,annotate,layer,utility,insert,view,file}.ts` 153개(별칭 82) → `commands/registry.ts:27-57` · 해석 `:116-120` = 이름 → 별칭 → 접두사 첫 후보 |
| 상태 기계 | xstate 5.18.1 · 원본 기계 20(선 `tools/line-tool.ts:50-110`) + 우리 공장 `tools/factories/sequence-tool.ts:161-359`(약 100 명령) |
| 프롬프트 | `meta.instructions` → `state.ts:297-308` → 커서 칸 · `CommandLine.tsx:87` |
`handleEnterKey :474-599` 차례: 빈 Enter(대기면 마지막 명령 반복 `:483-486`) → TEXT_INPUT 단계면 글 그대로 `:498-508` → 명령 이름 `:509` → 숫자 `:516` → `x,y :531` → `@x,y :548` → `@d<a :565` → `d<a :576` → 나머지 TEXT_INPUT `:587`.
| 항목 | 지금 | 근거 |
|---|---|---|
| 절대 · 상대 · 극좌표 | 선 · 원 · 사각형 · 배열 · 그립 · 시퀀스 됨 · 이동 · 복사 · 회전 안 됨 · 각도 동쪽 0° 반시계 + | `input-patterns.ts:7-17` |
| 직접 거리 | 선 · 폴리선 · 원 · 사각형 · 시퀀스 됨 · 이동 · 복사 안 됨 | `get-point-from-event.ts:25-37` |
| 각도 고정 `<30` | 안 됨 | `:587-597` |
| 동적 입력 | 안내 + 절대 좌표 + 친 글만 · 길이/각도 칸 · Tab 없음 · F12 = 개발자 도구 | `:127-142` · `:349-352` · `:384-387` |
| 직교 F8 · 극 추적 F10 | 90°/45° 가이드에 가까울 때만 붙음 · 각도 간격 없음 · F3 끄면 가이드 계산도 꺼짐 | `:369-383` · `helpers/get-draw-guides.ts:36-49` |
| 객체 스냅 | 끝 · 중간 · 교차 · 중심 · 사분 · 정렬선 됨 · END/MID 한 번 재정의 없음 | `helpers/calculate-angle-guides-and-snap-points.ts:21-59` |
| Enter · Space · 우클릭 · Esc | Space = Enter(글 단계 빼고) · 우클릭 = Enter(반복 안 함) · Esc = 선택 해제 → 글 지움 → 취소 | `:419-428` · `:150-156` · `:450-463` |
| 선 중 `U` · `C` | 안 됨 — `U` = 전체 UNDO 실행 · `C` = 원 명령 실행 | `commands/commands.file.ts:100-107` · `commands.draw.ts:51-58` |
| 이동 · 복사 · 회전 값 | 클릭만 · 축척만 배율 숫자 | `tools/move-tool.ts:146-169` · `copy-tool.ts:146-168` · `rotate-tool.ts:146-187` · `scale-tool.ts:178` |
| 숫자 형식 | `100` · `1.5` 만 · `-5` · `.5` 버려짐 | `input-patterns.ts:4` |
| 안내문 | 원본 도구 영어 · 우리 시퀀스 한글 · `[닫기(C)]` 같은 선택지 안내 없음 | `line-tool.ts:61,80` |
넣을 자리: 공통 점 계산 `getPointFromEvent`(`get-point-from-event.ts:18-49`) — 선 · 원 · 사각형 · 배열 · 그립 · 블록 · 시퀀스가 모두 거침 → 각도 고정을 여기에 넣으면 한 번에 퍼짐 · 안 거치는 곳 = 원본 이동 · 복사 · 회전(`move-tool.ts:194-199,251` · `copy-tool.ts:193-198,248` · `rotate-tool.ts:206-220,264`) · 미리보기가 마우스를 직접 읽는 곳 = `line-tool.ts:134` · `sequence-tool.ts:143-148,179`. 주의: 선택지(C/U)용 TEXT_INPUT 을 점 단계에 달면 숫자 · 좌표까지 글로 넘어감(`:498-508` 이 숫자 해석보다 앞) → 자유 글 단계(`ime-sink.ts:19-24`)만 먼저 보내게 차례 바꿈.
키 충돌: CAD = 같은 출처 iframe(`ui_template/cad_host/cad_host.ts:143-156`) · B07 페이지 keydown 없음 · 부모 칸을 누른 뒤엔 키가 CAD 로 안 감 · F5 · F11 · F12 · Tab · Ctrl+V 만 브라우저로(`:349-391`) · `<` `@` 숫자 `,` `.` `-` Enter Space 충돌 없음 · Tab 가로채면 초점 이동 뺏음 · 한글 자판 = 점 단계에서 IME 칸 초점 풂(`ime-sink.ts:72-81`). 같은 CAD 를 B06 횡단 상세 · B07 평면도 관리 · M02 도면도 씀(`createCadHost`) → 넷에 함께 반영.
반영: `npm --prefix B07_DesignDetail/openwebcad run build`(`tsc -b && vite build`) → `dist/` → FastAPI `/b07-cad`(`main.py:531-552`) → 새로고침만. 시험 틀 = vitest(`src/inputController/ime-sink.test.ts:58-140` · `test/helpers/init-application.ts` · `test/helpers/click.ts`) · `resources/tester/cad/`(ctrl-shortcut-hangul · grips · clone-keeps-look · geometry · 실행 `cd B07_DesignDetail/openwebcad && npx vitest run --dir ../../resources/tester/cad <이름>`) · 키보드 좌표 · 거리 시험 0건.
규모: src 228 파일 ≈27,800 줄 · 700 넘음 `App.css` 1,102 · 가까움 `screenCanvas.drawController.ts` 691 · `state.ts` 647 · `MeasurementEntity.ts` 622 · `input-controller.ts` 620. 원본 OpenWebCAD(MIT · 77ab85739 들여옴 · 원본과 연결 없음 → 원본 파일 고쳐도 됨).
## 조각 (코드 조사 제안)
1. 각도 고정 `<각도` → 길이 — 새 `src/inputController/input-angle-lock.ts`(고정 저장 · `lockPoint(base, p)` · 안내) · `input-patterns.ts` `ANGLE_LOCK_REGEXP` · `input-controller.ts` `:509` 앞 분기 · draw 안내 줄 · Esc 풂 · `get-point-from-event.ts` 고정 중 미리보기 · 클릭 · 숫자를 고정 방향으로 · 한 점 쓰면 풂 · `line-tool.ts:134` · `sequence-tool.ts:179` 미리보기 · `run-command.ts:40` 새 명령이면 풂 — 확인 = `resources/tester/cad/keyboard-chain.test.ts`(`0,0` · `<30` · `100` → (86.603, 50) · `<90` · `50` → (86.603, 100) · 고정 없이 `100` = 마우스 방향).
2. 시퀀스 첫 점 버그 — `sequence-tool.ts:284-285` NUMBER · RELATIVE 에 `!!lastPoint(context.values)` 조건.
3. 이동 · 복사 키보드 값 — 시작 ABSOLUTE · 끝 NUMBER · ABSOLUTE · RELATIVE · `getPointFromEvent` 로.
4. 회전 각도 숫자 — `rotate-tool.ts:156-174` NUMBER_INPUT · 음수 허용(`input-patterns.ts:4`).
5. (선택) 선 중 `C` · `U` · 6. 원본 안내문 한글화 + 「(길이 · <각도 · @x,y)」 · 7. 동적 입력 `100 Tab 30⏎`.
## 오토캐드 방식 (인터넷 조사 · Autodesk 도움말 우선)
- 네 겹: 명령줄(글자) → 프롬프트 대기(점 · 값 · 선택지) → 점 입력 문법(`x,y` · `@` · `<` · 숫자만) → 보조 모드(직교 · 극 추적 · 스냅). 프롬프트 하나가 클릭 · 좌표 · 거리 · 선택지 글자 · 스냅 이름을 다 받음.
- 명령줄: 이름 · 별칭(acad.pgp `A, *ARC` · 대소문자 무관) → Enter/Space · 자동완성(자주 쓴 순 · 중간 글자 · 지연 300ms) · ↑↓ 최근 입력 · 끝난 뒤 Enter/Space = 직전 명령 · Esc 취소 · 우클릭 = Enter/메뉴(시간 감지 짧게 Enter · 길게 메뉴) · 투명 명령 `'ZOOM`.
- 프롬프트: `Specify next point or [Close/Undo]:` · 대괄호 = 선택지 · 대문자 글자 = 입력 키 · `<값>` = 기본값(Enter 면 유지) · 선택지 클릭 · 동적 입력 ↓ 목록.
- 점 입력: 절대 `100,200` · 상대 `@30,0` · 극 `30<45` · 상대 극 `@30<45` · 각도 잠금 `<45` Enter → 커서로 쪽 → `30` Enter(다음 한 점만 · "Angle Override") · 직접 거리(커서 방향 + `30` Enter · 직교 · 극 추적과 같이) · 각도 = 동쪽 0° 반시계 + · 도분초 `45d30'15"` · 방위 `N45d30'E` · 소수점 `.` 만(`,` = 좌표 구분).
- 동적 입력(F12): 둘째 점부터 기본 상대 극(`@` 생략) · 절대는 `#` · 치수 입력 = 길이 · 각도 칸 · 값 넣고 Tab = 그 칸 잠금 + 다음 칸 · Enter 확정.
- 보조 모드: 극 추적 F10(증분 90 · 60 · 45 · 30 · 22.5 · 18 · 15 · 10 · 5° · 추가각) · 직교 F8(Shift 누르는 동안 반전) · 객체 스냅 F3 + 점 프롬프트에서 END · MID · CEN · INT · PER · TAN · NEA · QUA · NOD(다음 한 점만) · NON · 스냅 추적 F11 · 임시 덮어쓰기 Shift+키.
- LINE 「선 → 각도 → 길이」 키 차례: 동적 입력 켬 = `L` Enter → 첫 점 → Tab `45` Tab(각도 잠김) `30` Enter 또는 `30` Tab `45` Enter 또는 `30<45` Enter · 끔 = `L` Enter → 첫 점 → `<45` Enter → 커서로 쪽 → `30` Enter 또는 `@30<45` Enter · `U` Enter = 마지막 선 취소 · `C` Enter = 첫 점으로 닫고 끝 · Enter · Esc = 끝 · 새 LINE 첫 점에서 Enter = 마지막 끝점에서 이어 그림.
- 비슷한 프로그램: BricsCAD(같은 명령줄 · 동적 치수 길이 → Tab 잠금 → 각도 · F12 = Quad) · DraftSight(Quick Input F12 · Tab) · LibreCAD(`@100<90` · `@` 없으면 절대) · nanoCAD(`300` Tab `30` Enter) · Onshape(명령줄 없음 · 그린 뒤 치수 구속) · AutoCAD Web(명령줄 · 선택지 단추).
- 웹 함정: 브라우저 F키(F1 · F3 · F5 · F6 · F7 · F10 · F11 · F12 · 막을 수 있는 건 캔버스 초점 때 preventDefault · 실측 필요) · Ctrl+W · T · N 못 막음 · 한글 IME(key "Process" · keyCode 229 · `event.code` 우선 · 조합 중 Enter 무시) · y축 아래(내부 수학각 하나 · 화면에서 뒤집기) · 우클릭 · 가운데 누름 기본 동작 막기.
- 사용자 지정(설정 모달 후보): 별칭 편집(acad.pgp User Defined · 같은 별칭은 마지막이 이김) · 단축키(CUI) · 우클릭 모드 · 시간 · 휠 줌 방향(ZOOMWHEEL 0 = 앞으로 굴림 확대 · 1 = 반대) · 비율(ZOOMFACTOR 60) · 가운데 끌기 = 팬(MBUTTONPAN 1) · 동적 입력 기본(상대/절대 · 극/직교) · 극 추적 각 · 각 형식.
- 추천 사양: 1단계 = 명령줄 · 별칭 + 자동완성 · 프롬프트 `[닫기(C)/명령취소(U)] <기본값>` · 점 파서 한 벌(`x,y` · `@dx,dy` · `d<a` · `@d<a` · `<a` · 숫자 · 음수 · 동쪽 0° 반시계) · LINE U · C · 첫 점 Enter 이어 그림 · 직교 · 한 번 스냅 이름 · IME · 우클릭 = Enter · 2단계 = 동적 입력 길이 Tab 각도 · 극 추적 · Shift 직교 · 최근 입력 · 3단계 = 스냅 추적 · FROM · 자동교정 · 설정 모달(별칭 · 단축키 · 우클릭 · 휠 · 동적 입력 기본 · 각 형식).
- 출처(대표): 명령줄 help.autodesk.com/cloudhelp/2026/ENU/AutoCAD-LT/files/GUID-BB0C3E79-66AF-4557-9140-D31B4CF3C9CF.htm · 각도 잠금 · 직접 거리 …/2027/ENU/AutoCAD-LT/files/GUID-57CDDB6C-B12B-46CE-B9C5-22EFC17258FF.htm · 극좌표 …/2018/ENU/AutoCAD-LT/files/GUID-58C1D97C-A9B3-4C3C-AC1B-95BE3DF2EDB9.htm · 동적 입력 …/2027/ENU/AutoCAD-LT/files/GUID-3EBD4C17-F0A5-49FA-B131-4AABE2E727DB.htm · 치수 입력 …/2021/ENU/AutoCAD-LT/files/GUID-A265C30C-CFD8-405D-BEE2-257B379475E2.htm · 극 추적 …/2027/ENU/AutoCAD-LT/files/GUID-7EC3C63D-EA4E-4E65-A676-C3A3627E3F19.htm · 객체 스냅 …/2018/ENU/AutoCAD-LT/files/GUID-8F5E5431-9EFB-414E-BC6D-2C65EFB2DAC3.htm · 별칭 편집 …/2025/ENU/AutoCAD-Customization/files/GUID-D4CACED6-DFBA-43C3-BC42-8D980AB3AE75.htm · 우클릭 …/2027/ENU/AutoCAD-LT/files/GUID-7C6AE349-DDF0-40DF-9D1E-FA9702BE88BB.htm · 휠 · 가운데 …/2018/ENU/AutoCAD-LT/files/GUID-B1CDEE41-F8BF-48A4-A895-273DBB295378.htm · Chrome 단축키 support.google.com/chrome/answer/157179 · KeyboardEvent.code developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code. 약한 근거: AutoCAD Web · DraftSight 원문 503(검색 요약) · 별칭 표는 옛 acad.pgp 사본(공식 확인 L · C · E · LA) · F키 가로채기 실측 필요.
@@ -0,0 +1,64 @@
# 332 ☰ [설정] 모달 — 마우스 · 단축키 (요구 224-8)
기준 = 837fe3d64.
## ☰ 서랍 · 모달 · 탭
- 서랍 한 벌: `A00_Common/app_shell.ts:149-210` `buildDrawer` · 차례(`:181-208`) = 로고 · 진행 단계(작업 흐름 화면만 · `ui_template/ui_template_workflow_layout.ts:203`) · 메뉴 5(`:127-133`) · 설정 묶음(`:205-207` = 언어 · 테마 `:190-201` · 계정 `:250-299` · 터치 모드 `:212-247`) · 주소 바뀌면 다시 그림(`main.ts:78`) · 권한 갈래 없음 · 닫힘 = 로고 · 단계 · 메뉴 · 계정 누름(`:175-179`) · Esc(`:173`) · 바탕(`:172`).
- 더하는 법: 설정 묶음(`:207`)에 `createButton` 한 줄 · 문구 `ui_template/ui_template_locale_common.ts:103-111` · 누르면 닫히려면 `:177` 선택자에 클래스.
- 모달 `ui_template/ui_template_modal.ts:12-62` `openModal`(actions · wide · beforeClose · z 1000 > 서랍 900) · 저장 안 한 고침 물음 `ui_template/ui_template_leave_dialog.ts:13` `askLeave`(예 `B08_Quantity_UI_Drawing.ts:210`).
- 탭 공용 부품 없음 · 공용 모양 `.ui-subtabs`(`ui_template_general_layout.css:58-70`) + `createButton` filled/ghost + `role=tab`(예 `B08_Quantity_UI_Milestone.ts:180-200` · `B09_Estimation_UI_TabBar.ts:30-60` · `M02_DF_InputPanel.ts:144-155`).
## 마우스 현황 (당김 = 휠을 몸 쪽으로 굴리면 확대)
| 화면 | 왼쪽 | 가운데 | 오른쪽 | 휠 | 근거 |
|---|---|---|---|---|---|
| B04 2D 지도 | 고름 · 관 끌기 | 팬 | 메뉴 | 줌 · 당김 | `B04_PreProcess_UI_MapViewer.ts:536-625` |
| B04 · B05 3D | 회전 · 고름 | 팬 | 배치 메뉴 | 줌 · 당김 | `B04_PreProcess_UI_Camera.ts:140-254` · `B05_Profile_UI_Viewer.ts:145,266` |
| B05 배수유역도 | 관 끌기 · 고름 | 팬 | 메뉴 | 줌 · 당김 | `B05_Profile_UI_Drainage_Interact.ts:60-119` |
| B05 노선 편집 지도 | 노드 끌기 · 두 번 = 더함 | 팬 | 노드 지움 | 줌 · 당김 | `B05_Profile_UI_RouteEdit_Input.ts:49-146` |
| 노선 편집 횡단 · B06 [상세] | 고름 | 팬 | — | 줌 · 당김 · 두 번 = 원배율 | `A00_Common/b_svg_zoom_pan.ts:162-264` |
| B05 · B06 종단 · 유토 · 측점표 | 가로 이동 | 가로 이동 | 메뉴 | 가로 스크롤 · 확대는 단추 | `B05_Profile_UI_Profile_Wheel.ts:82-177` |
| B06 횡단 카드 | 고름 | 팬 | 메뉴 | 목록 스크롤 | `B06_Section_UI_Cross_View_Zoom.ts:51-100` |
| CAD(B07 · B06 상세 · M02 도면) | 고름 · 점 | 팬 | Enter | 줌 · 당김 | `openwebcad/src/inputController/input-controller.ts:150-300` · `input-wheel.ts:20-23` |
| B08 거리이정표 | 빈 곳 끌기 = 팬 | 팬 | — | 줌 · 당김 | `B08_Quantity_UI_Milestone_Diagram.ts:157,463-474` |
| B08 · B09 표 | 고름 · 두 번 = 입력 | — | 메뉴 | 스크롤 | `ui_template/ui_template_data_table.ts:514-529` |
| M02 DF 그림 | 상자 끌기 · Space+왼쪽 = 팬 | 팬 | — | 줌 · 밀기 | `M02_DF_Diagram.ts:57-66,453-476` |
| M02 노무 그림 | Space+왼쪽 = 팬 | 팬 | — | Ctrl+휠 · 밀기 | `M02_MasterTemplete_Labor_Diagram.ts:283-332` |
| M02 시트 | 칸 고름 | — | 메뉴 | Ctrl+휠 · 밀기 | `A00_Common/spreadsheet/spreadsheet_grid.ts:270-284` |
| 공용 돋보기 | — | — | 0.4초 누름(Z 키도) | 배율 · 밀기 | `ui_template/ui_template_loupe.ts:5-93` |
- 팬 = 가운데 단추로 통일 · 휠 방향 둘로 갈림 · 공용 판정 `A00_Common/touch_bridge.ts:35-36` `wheelZoomsIn` 은 셋만 씀(B04 지도 · 배수유역도 · 노선 편집 지도) · 나머지 직접 박음(`b_svg_zoom_pan.ts:217` · `B04_PreProcess_UI_Camera.ts:253` · `input-wheel.ts:23` · `M02_DF_Diagram.ts:457` · `Labor_Diagram.ts:316` · `ui_template_loupe.ts:91` · `spreadsheet_grid.ts:274`).
- 지금 사용자가 바꿀 수 있는 것: 터치 모드(localStorage `frd_touch_mode` · `device_context.ts:33,91-101`) · B05 3D 투영 · B09 행 누름(pref `b_page_state.ts:71,107`) — 마우스 단추 · 휠 방향 칸 없음. CAD 휠 지수 · 스냅 거리 상수(`openwebcad/src/App.consts.ts:52,150-160`) · 토글은 메모리(`state.ts:82,179-180`). maplibre 안 씀(평면 = 자체 캔버스).
## 단축키 현황
| 자리 | 키 | 근거 |
|---|---|---|
| 공용 판정 | Ctrl+Z · Shift+Z · Y · S · A(한글 자판 자리) | `A00_Common/key_shortcut.ts:28-49` |
| 전역 | Esc = ☰ · 창 닫힘 · 고름 풂 · Z 누름 = 돋보기 | `app_shell.ts:173` · `ui_template_modal.ts:53` · `design/design_pick_escape.ts:15-29` · `ui_template_loupe.ts:66-72` |
| B05 종단 | Ctrl+Z · Ctrl+Shift+Z · 방향키 · 자유지정 Enter · Esc · Backspace | `B05_Profile_UI_Profile_Panel_Tools.ts:310-349` |
| B05 노선 편집 | Ctrl+Z · Y · Shift+Z | `B05_Profile_UI_RouteEdit_History.ts:157-165` |
| B06 | 방향키 · ▲▼ · Enter/Space · Esc | `B06_Section_UI_Cross_Panel_Base.ts:264-290` · `_Cross_Design_Pick.ts:545-562` |
| CAD | F3 · F7 · F8 · F10 · Ctrl+Z · Y · A · S(M02) · V · Delete · Esc · Enter · Space = Enter · 방향키 · 글자 = 명령 | `input-controller.ts:364-447` |
| CAD 별칭 | 명령 153 · 별칭 82 · `commands.*.ts` `aliases` | `openwebcad/src/commands/registry.ts:50-57,116-135` |
| B08 · B09 · 표 | Enter · F2 · 방향키 · Esc | `ui_template_data_table.ts:520-529` 외 |
| M02 | Ctrl+S · Z · C/V · Delete · Esc · Space = 팬 · 시트 엑셀 키 | `M02_MasterTemplete_CardShell.ts:79-80` · `spreadsheet_keys.ts` |
정의 표 = CAD 별칭 하나(엑셀 키 표 `spreadsheet_shortcuts.ts:1-10` 은 대조용) · 충돌 관리 없음(입력 칸 건너뜀 · `defaultPrevented` 양보 · 창 떠 있으면 양보 · CAD 는 iframe · 별칭 겹치면 먼저 것 `registry.ts:53-56` · 이름 > 별칭 `:119`).
## 저장 — 새 표 · API 필요 없음
- 표 `user_ui_prefs`(`db_management/018_user_ui_prefs.sql:17-25`) · `GET/PUT /api/dashboard/me/ui-prefs`(`B01_Dashboard_Router.py:122-133` · 값 문자열 · 통째 덮음 `Repository.py:554-573`).
- 브라우저 `pref` 통 = localStorage `aislo:pref:*`(`A00_Common/b_page_state.ts:47-112,285-287`) · 바뀌면 1.5초 모아 PUT(`:337-375`) · 로그인 첫 세션에 서버 값 얹음(`A06_Login_Api_Fetch.ts:64-69` → `b_page_state.ts:378-404`) · CAD iframe 같은 오리진 → localStorage 같이 씀(테마 선례 `openwebcad/src/theme.ts:43-52`).
## 설계 후보 · 위험
- 읽기 층 새 `A00_Common/input_prefs.ts`(의존 없음 · CAD 도 import) — `INPUT_PREFS_KEY` · `DEFAULT_INPUT_PREFS`(= 지금 동작) · `inputPrefs()`(부를 때마다 읽음 · 깨짐/Node = 기본값) · `wheelIn(deltaY)` · 모달 [저장] 때만 `writeState("input-prefs", 값)`.
- 배선 자리: 휠 = `touch_bridge.ts:36`(셋 자동) · `b_svg_zoom_pan.ts:217` · `B04_PreProcess_UI_Camera.ts:253` · `input-wheel.ts:23` · (일체면) M02 둘 · 돋보기 · 시트 · CAD 별칭 = `registry.ts:116-131`(이름 > 내 별칭 > 기본) · CAD 오른쪽 = `input-controller.ts:155`.
- 위험: 700줄(`B04_PreProcess_UI_MapViewer.ts` 699 안 만짐 · `M02_DF_Diagram.ts` 692 한 줄 · `input-controller.ts` 620) · 공용 파일(`b_page_state.ts` · `app_shell.ts` · `touch_bridge.ts` · locale) 한 창이 맡음 · CAD `input-controller.ts` · `registry.ts` 는 331 과 겹침 → 331 뒤 · 휠 일체면 M02 그림 · 시트 · 돋보기 기본 방향 반대 · 계정 전환 시 앞 사람 값 남을 수 있음(기존 한계) · Node 시험(helper_164 · 266 등)이 `b_svg_zoom_pan` · `touch_bridge` 를 불러 돌림 → `input_prefs` 는 불러올 때 window 안 만짐 · CAD 명령 목록은 `commands.*.ts` import 시 xstate 딸려 옴 → 목록 JSON 한 벌 + 거울 시험.
## 시험
- 있음: `test_device_drawer.py`(서랍 차례) · `test_device_audit.py` · `test_touch_bridge.py` · `test_cad_touch.py` · `test_prefs_persist_across_tabs.py` · `test_b01_ui_prefs_dead_project.py` · `test_session_keys_registered.py` · 휠 · 팬(`test_164_detail_zoom.py` · `test_266_detail_home.py` · `test_281_1_drainage_zoom_range.py` · `test_230_1_popout_wheel.py` · `test_244_6/8_*` · `test_b05_routeedit_cross_width.py` · `test_cross_axes_zoom.mjs` · `test_cross_pan_bounds.mjs` · `openwebcad/src/inputController/input-wheel.test.ts`) · 돋보기 · 키 · 모달(`test_218_1_loupe.py` · `test_220_1` · `test_221_1` · `cad/ctrl-shortcut-hangul.test.ts` · `cad/ctrl-shortcut-sheet.test.ts` · `test_modal_head_actions.py` · `test_cad_host_tabs.py`).
- 새로: `test_input_prefs.py`(+ cjs) · `cad/input-prefs-cad.test.ts` · `test_app_settings.py`.