chore: import 3d-drawing history from local Code/3d-drawing (subfolder, history preserved)
This commit is contained in:
commit
7b754e810b
51 files changed
+20300
No files matched your search
@@ -0,0 +1,5 @@
|
||||
data/*.tmp
|
||||
__pycache__/
|
||||
*.pyc
|
||||
model/parts/
|
||||
model/tunnel_solids.step
|
||||
@@ -0,0 +1,56 @@
|
||||
# 대산2터널 본선 3D 솔리드 모델 (Code/3d-drawing)
|
||||
|
||||
대상: 본선 4+745 ~ 5+480 (당진방향), 도면 C0060303-001 기준. 판정 근거는 `DOC/05_검증/검증_본선3D솔리드모델_2026-10-22_v0.1.00.md`.
|
||||
|
||||
## 실행 (뷰어)
|
||||
|
||||
1. 이 폴더(`Code/3d-drawing`)에서 HTTP 서버를 엽니다. 예: `python3 -m http.server 8000`
|
||||
2. 브라우저에서 `http://localhost:8000/viewer/` 를 엽니다.
|
||||
- `file://`로 열면 GLB를 불러올 수 없습니다.
|
||||
- three.js 0.160.0 (jsDelivr CDN) 사용. 인터넷 연결 필요.
|
||||
|
||||
## 산출물 (model/)
|
||||
|
||||
- `tunnel_solids.step` : B-rep 솔리드 320개 (build123d 0.13, 91.7 MB, git 제외)
|
||||
- `tunnel_solids.glb` : 뷰어용 지오메트리 326개
|
||||
- `tunnel_solids_meta.json` : 구성요소 목록, 지보패턴 구간 40개, 가정·미확정 항목
|
||||
|
||||
구성요소: 라이닝 · 굴착 여유대 · 노면 포장 280 mm · 기층·인버트(가정) · 배수 측구 L/R · 공동구 H/I(가정) · 시설한계 50 mm 참조선(솔리드 아님, 6개).
|
||||
구성요소는 지보패턴 구간 경계(40구간)에서 분할되어 있습니다.
|
||||
|
||||
## 데이터 (data/)
|
||||
|
||||
- `pattern_stations_dangjin.csv` : 당진방향 지보패턴 측점표 (42행, 개착부 포함)
|
||||
- `verify_solids.json` : 모델 생성 검증 요약 (솔리드 수, 구간, 부피 합)
|
||||
- `verify_geom_hd2_results.json` : 도면↔모델 단면 편차 측정 결과 (4+745, 5+100, 5+470)
|
||||
- `verify_lining_judgement.md` : 라이닝 도면 판독 판정 (이전 Task)
|
||||
|
||||
## 스크립트 (scripts/)
|
||||
|
||||
- `build_solids.py` : B-rep 솔리드 생성 (모드: parts_a, parts_b, mesh, step, final)
|
||||
- `verify_solids_step3.py` : 결정론 점검 M1~M6, S1~S2, G1 (9/9 PASS)
|
||||
- `verify_geom/` : 도면↔모델 단면 편차 측정 스크립트 (독립 에이전트 실행)
|
||||
- `verify_lining.py`, `verify_viewer_headless.py` : 라이닝 판독·뷰어 헤드리스 점검 (이전 Task)
|
||||
- 그 밖의 `extract_*.py`, `inventory_dxf.py`, `dump_std_table.py` 등은 DXF·엑셀 추출 스크립트
|
||||
|
||||
## 검증 결과 요약 (2026-10-22)
|
||||
|
||||
- 결정론 점검 9/9 PASS (생성 세션 자기검증).
|
||||
- 도면 기준 편차 (±50 mm 허용):
|
||||
- 라이닝 내측·상부·내측 헌치: 기준 이내 (17.5~21.5 mm)
|
||||
- 라이닝 외측 하단 헌치 호: **미모델링, 최대 492 mm** — 사용자 결정으로 한계 수용
|
||||
- 기층 하면: **최대 105 mm** — 사용자 결정으로 한계 수용
|
||||
- 배수 측구 본체: 기준 이내 (14.5 mm). 상부 덮개 (도면 폭 450 mm, 78 mm)는 **설계 범위에서 제외** (사용자 결정)
|
||||
- 굴착·노면 포장: 기준 이내 (14~22 mm)
|
||||
- 편경사: 4+745·5+100은 도면 부호와 일치. 5+470 +2%는 도면 −2%와 상이 — **현 가정 유지** (사용자 결정, 미확정).
|
||||
|
||||
## 한계 (반드시 읽을 것)
|
||||
|
||||
- 공동구 H/I 박스는 **가정**입니다. 03-001에 박스 외형이 없고, 04-001~003 공동구·배수 계획도는 아직 대조하지 않았습니다.
|
||||
- 기층·인버트 형상·두께는 **가정**입니다 (위 편차 한계 수용).
|
||||
- 라이닝 구조도 상세(04-004~009)의 선분 기하는 대조하지 않았습니다. 판정은 03-001 기준입니다.
|
||||
- 굴착 지보 등급(NP-4/5/6)은 모델에 반영하지 않았습니다. 굴착 여유대는 대칭 가정입니다.
|
||||
- 라이닝·굴착 솔리드 사이에 약 0.0173 m² 겹침이 있습니다 (단순화).
|
||||
- 측점 간 축선은 선형 보간, 단면은 10 m 간격 Ruled loft 근사입니다 (곡선부 근사).
|
||||
- 뷰어: 1366×768 해상도에서 좌측 패널이 약 60 px 넘칩니다. 버튼 높이 36 px (WCAG AA 24 px 충족, AAA 44 px 미충족). Pretendard 폰트는 내장하지 않았습니다.
|
||||
- 미확정 패턴(E-1, E-2, RP-2, WL-3)과 NP-5BE-1-WL-4 해석은 당진 표 밖이거나 확인 필요입니다 (`OPEN_미해결목록.md` 참조).
|
||||
@@ -0,0 +1,10 @@
|
||||
station_m,centre_x,centre_y,side_gap_m
|
||||
4700,151067.261,483726.735,8.554
|
||||
4800,151119.817,483641.679,11.191
|
||||
4900,151169.128,483554.701,13.981
|
||||
5000,151215.122,483465.925,16.844
|
||||
5100,151258.471,483375.815,18.211
|
||||
5200,151301.3,483285.451,17.956
|
||||
5300,151344.993,483195.51,15.809
|
||||
5400,151390.045,483106.254,12.322
|
||||
5500,151437.284,483018.131,9.389
|
||||
|
@@ -0,0 +1,10 @@
|
||||
station_m,centre_x,centre_y,side_gap_m
|
||||
4700,151067.261,483726.735,8.554
|
||||
4800,151119.817,483641.679,11.191
|
||||
4900,151169.128,483554.701,13.981
|
||||
5000,151215.122,483465.925,16.844
|
||||
5100,151258.471,483375.815,18.211
|
||||
5200,151301.3,483285.451,17.956
|
||||
5300,151344.993,483195.51,15.809
|
||||
5400,151390.045,483106.254,12.322
|
||||
5500,151437.284,483018.131,9.389
|
||||
|
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,256 @@
|
||||
{
|
||||
"source": "C0060304-004..009 (콘크리트라이닝구조도)",
|
||||
"sheets": [
|
||||
{
|
||||
"sheet": 4,
|
||||
"file": "C0060304-004-콘크리트라이닝구조도(1)(2차로,자연환기300m이상).dxf",
|
||||
"pattern_details": [
|
||||
{
|
||||
"group": "A~C-2",
|
||||
"kind": "입출구부 50m",
|
||||
"raw": "주철근 조립도(A~C-2패턴) 입출구부 50m"
|
||||
}
|
||||
],
|
||||
"note5": "5. 본 콘크리트 라이닝 구조도는 지보패턴 입출구부 A~C-2 구간의 구조적 취약구간에 적용하며, 시공시 지질이상대 출현 등으로 설계조건과 상이할 경우 구조해석 검토를 통해 감독원과 협의하여 철근을 보강할 수 있다.",
|
||||
"concrete": [
|
||||
"사용재료 : 콘크리트 fck=27MPa"
|
||||
],
|
||||
"rebar_grade": [
|
||||
"철 근 fy=400MPa"
|
||||
],
|
||||
"aggregate": [
|
||||
"조골재의 최대치수 Ø25"
|
||||
],
|
||||
"rebar_sizes_label_count": {
|
||||
"H13": 12,
|
||||
"H16": 15
|
||||
},
|
||||
"radius_dims": [
|
||||
"R1=6.892",
|
||||
"R2'=3,910",
|
||||
"R2=3,712"
|
||||
],
|
||||
"lap_dims": [
|
||||
"600mm"
|
||||
],
|
||||
"el_texts": [
|
||||
"EL. 0.000",
|
||||
"EL.-0.367"
|
||||
],
|
||||
"entrance_exit_50m": true
|
||||
},
|
||||
{
|
||||
"sheet": 5,
|
||||
"file": "C0060304-005-콘크리트라이닝구조도(2)(2차로,자연환기300m이상).dxf",
|
||||
"pattern_details": [
|
||||
{
|
||||
"group": "E-1~2",
|
||||
"kind": "온도미고려",
|
||||
"raw": "주철근 조립도(E-1~2 패턴) 온도미고려"
|
||||
},
|
||||
{
|
||||
"group": "D-1~2",
|
||||
"kind": "입출구부 50m",
|
||||
"raw": "(D-1~2 패턴) 입출구부 50m"
|
||||
}
|
||||
],
|
||||
"note5": "5. 본 콘크리트 라이닝 구조도는 지보패턴 입출구부 D-1,2, 온도미고려 E-1,2 구간의 구조적 취약구간에 적용하며, 시공시 지질이상대 출현 등으로 설계조건과 상이할 경우 구조해석 검토를 통해 감독원과 협의하여 철근을 보강할 수 있다.",
|
||||
"concrete": [
|
||||
"사용재료 : 콘크리트 fck=27MPa"
|
||||
],
|
||||
"rebar_grade": [
|
||||
"철 근 fy=400MPa"
|
||||
],
|
||||
"aggregate": [
|
||||
"조골재의 최대치수 Ø25"
|
||||
],
|
||||
"rebar_sizes_label_count": {
|
||||
"H13": 6,
|
||||
"H16": 21
|
||||
},
|
||||
"radius_dims": [
|
||||
"R1=6.892",
|
||||
"R2'=3,910",
|
||||
"R2=3,712"
|
||||
],
|
||||
"lap_dims": [
|
||||
"600mm"
|
||||
],
|
||||
"el_texts": [
|
||||
"EL. 0.000",
|
||||
"EL.-0.367"
|
||||
],
|
||||
"entrance_exit_50m": true
|
||||
},
|
||||
{
|
||||
"sheet": 6,
|
||||
"file": "C0060304-006-콘크리트라이닝구조도(3)(2차로,자연환기300m이상).dxf",
|
||||
"pattern_details": [
|
||||
{
|
||||
"group": "WL-1~2,RP-1~2",
|
||||
"kind": "온도미고려",
|
||||
"raw": "주철근 조립도(WL-1~2,RP-1~2패턴) 온도미고려"
|
||||
},
|
||||
{
|
||||
"group": "E-1~2",
|
||||
"kind": "입출구부 50m",
|
||||
"raw": "(E-1~2 패턴) 입출구부 50m"
|
||||
}
|
||||
],
|
||||
"note5": "5. 본 콘크리트 라이닝 구조도는 지보패턴 입출구부 E-1~2 온도미고려 WL-1,2 RP-1,2 구간의 구조적 취약구간에 적용하며, 시공시 지질이상대 출현 등으로 설계조건과 상이할 경우 구조해석 검토를 통해 감독원과 협의하여 철근을 보강할 수 있다.",
|
||||
"concrete": [
|
||||
"사용재료 : 콘크리트 fck=27MPa"
|
||||
],
|
||||
"rebar_grade": [
|
||||
"철 근 fy=400MPa"
|
||||
],
|
||||
"aggregate": [
|
||||
"조골재의 최대치수 Ø25"
|
||||
],
|
||||
"rebar_sizes_label_count": {
|
||||
"H13": 6,
|
||||
"H16": 15,
|
||||
"H19": 7
|
||||
},
|
||||
"radius_dims": [
|
||||
"R1=6.892",
|
||||
"R2'=3,910",
|
||||
"R2=3,712"
|
||||
],
|
||||
"lap_dims": [
|
||||
"600mm"
|
||||
],
|
||||
"el_texts": [
|
||||
"EL. 0.000",
|
||||
"EL.-0.367"
|
||||
],
|
||||
"entrance_exit_50m": true
|
||||
},
|
||||
{
|
||||
"sheet": 7,
|
||||
"file": "C0060304-007-콘크리트라이닝구조도(4)(2차로,자연환기300m이상).dxf",
|
||||
"pattern_details": [
|
||||
{
|
||||
"group": "WL-1~2",
|
||||
"kind": "입출구부 50m",
|
||||
"raw": "주철근 조립도(WL-1~2패턴) 입출구부 50m"
|
||||
},
|
||||
{
|
||||
"group": "WL-3~4",
|
||||
"kind": "온도미고려",
|
||||
"raw": "(WL-3~4패턴) 온도미고려"
|
||||
}
|
||||
],
|
||||
"note5": "5. 본 콘크리트 라이닝 구조도는 지보패턴 입출구부 WL-1,2 온도미고려 WL-3,4 구간의 구조적 취약구간에 적용하며, 시공시 지질이상대 출현 등으로 설계조건과 상이할 경우 구조해석 검토를 통해 감독원과 협의하여 철근을 보강할 수 있다.",
|
||||
"concrete": [
|
||||
"사용재료 : 콘크리트 fck=27MPa"
|
||||
],
|
||||
"rebar_grade": [
|
||||
"철 근 fy=400MPa"
|
||||
],
|
||||
"aggregate": [
|
||||
"조골재의 최대치수 Ø25"
|
||||
],
|
||||
"rebar_sizes_label_count": {
|
||||
"H13": 6,
|
||||
"H16": 8,
|
||||
"H19": 7,
|
||||
"H22": 7
|
||||
},
|
||||
"radius_dims": [
|
||||
"R1=6.892",
|
||||
"R2'=3,910",
|
||||
"R2=3,712"
|
||||
],
|
||||
"lap_dims": [
|
||||
"600mm"
|
||||
],
|
||||
"el_texts": [
|
||||
"EL. 0.000",
|
||||
"EL.-0.367"
|
||||
],
|
||||
"entrance_exit_50m": true
|
||||
},
|
||||
{
|
||||
"sheet": 8,
|
||||
"file": "C0060304-008-콘크리트라이닝구조도(5)(2차로,자연환기300m이상).dxf",
|
||||
"pattern_details": [
|
||||
{
|
||||
"group": "WL-3~4",
|
||||
"kind": "입출구부 50m",
|
||||
"raw": "주철근 조립도(WL-3~4패턴) 입출구부 50m"
|
||||
}
|
||||
],
|
||||
"note5": "5. 본 콘크리트 라이닝 구조도는 지보패턴 입출구부 WL-3,4 구간의 구조적 취약구간에 적용하며, 시공시 지질이상대 출현 등으로 설계조건과 상이할 경우 구조해석 검토를 통해 감독원과 협의하여 철근을 보강할 수 있다.",
|
||||
"concrete": [
|
||||
"사용재료 : 콘크리트 fck=27MPa"
|
||||
],
|
||||
"rebar_grade": [
|
||||
"철 근 fy=400MPa"
|
||||
],
|
||||
"aggregate": [
|
||||
"조골재의 최대치수 Ø25"
|
||||
],
|
||||
"rebar_sizes_label_count": {
|
||||
"H13": 6,
|
||||
"H16": 8,
|
||||
"H22": 7,
|
||||
"H25": 7
|
||||
},
|
||||
"radius_dims": [
|
||||
"R1=6.892",
|
||||
"R2'=3,910",
|
||||
"R2=3,712"
|
||||
],
|
||||
"lap_dims": [
|
||||
"600mm"
|
||||
],
|
||||
"el_texts": [
|
||||
"EL. 0.000",
|
||||
"EL.-0.367"
|
||||
],
|
||||
"entrance_exit_50m": true
|
||||
},
|
||||
{
|
||||
"sheet": 9,
|
||||
"file": "C0060304-009-콘크리트라이닝구조도(6)(2차로,자연환기300m이상).dxf",
|
||||
"pattern_details": [
|
||||
{
|
||||
"group": "P-2~3",
|
||||
"kind": "입출구부 50m",
|
||||
"raw": "주철근 조립도(P-2~3 패턴) 입출구부 50m"
|
||||
}
|
||||
],
|
||||
"note5": "5. 본 콘크리트 라이닝 구조도는 지보패턴 입출구부 P-2~3 구간의 구조적 취약구간에 적용하며, 시공시 지질이상대 출현 등으로 설계조건과 상이할 경우 구조해석 검토를 통해 감독원과 협의하여 철근을 보강할 수 있다.",
|
||||
"concrete": [
|
||||
"사용재료 : 콘크리트 fck=27MPa"
|
||||
],
|
||||
"rebar_grade": [
|
||||
"철 근 fy=400MPa"
|
||||
],
|
||||
"aggregate": [
|
||||
"조골재의 최대치수 Ø25"
|
||||
],
|
||||
"rebar_sizes_label_count": {
|
||||
"H13": 6,
|
||||
"H16": 8,
|
||||
"H22": 4,
|
||||
"H25": 4,
|
||||
"H29": 8
|
||||
},
|
||||
"radius_dims": [
|
||||
"R1=6.892",
|
||||
"R2'=3,910",
|
||||
"R2=3,712"
|
||||
],
|
||||
"lap_dims": [
|
||||
"600mm"
|
||||
],
|
||||
"el_texts": [
|
||||
"EL. 0.000",
|
||||
"EL.-0.367"
|
||||
],
|
||||
"entrance_exit_50m": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
direction,source_row,sta_from,sta_to,length_m,lining_pattern,excavation,excavation_inherited,method,remark
|
||||
대산,13,4740.85,4758.85,18,P-3,NP-6,False,"상·하
|
||||
반단면",
|
||||
대산,14,4758.85,4768.85,10,WL-4,NP-5,False,,
|
||||
대산,15,4768.85,4792.85,24,WL-2,NP-5,True,,
|
||||
대산,16,4792.85,4839.85,47,D-2,NP-4,False,,
|
||||
대산,17,4839.85,4848.85,9,D-1,NP-4,True,,
|
||||
대산,18,4848.85,4857.85,9,C-2,NP-4,True,전단면,
|
||||
대산,19,4857.85,4862.85,5,C-1,C-1,False,,
|
||||
대산,20,4862.85,4868.85,6,B-2,B-2,False,,
|
||||
대산,21,4868.85,4874.85,6,B-1,B-1,False,,
|
||||
대산,22,4874.85,4908.85,34,A,A,False,,
|
||||
대산,23,4908.85,4916.67,7.819999999999709,B-1,B-1,False,,
|
||||
대산,24,4916.67,4936.67,20,RP-1,RP-1,False,,"대인용
|
||||
연결통로 접속부"
|
||||
대산,25,4936.67,4946.85,10.180000000000291,C-1,C-1,False,,
|
||||
대산,26,4946.85,4965.85,19,C-2,C-2,False,,
|
||||
대산,27,4965.85,4979.85,14,C-1,C-1,False,,
|
||||
대산,28,4979.85,4987.950000000001,8.100000000000364,B-2,B-2,False,,
|
||||
대산,29,4987.950000000001,4995.950000000001,8,B-1,B-1,False,,
|
||||
대산,30,4995.950000000001,5107.2300000000005,111.27999999999975,A,A,False,,
|
||||
대산,31,5107.2300000000005,5127.2300000000005,20,RP-1,RP-1,False,,"차량용
|
||||
연결통로 접속부"
|
||||
대산,32,5127.2300000000005,5139.450000000001,12.220000000000255,B-1,B-1,False,,
|
||||
대산,33,5139.450000000001,5163.450000000001,24,B-2,B-2,False,,
|
||||
대산,34,5163.450000000001,5205.450000000001,42,C-1,C-1,False,,
|
||||
대산,35,5205.450000000001,5235.450000000001,30,B-2,B-2,False,,
|
||||
대산,36,5235.450000000001,5279.35,43.899999999999636,B-1,B-1,False,,
|
||||
대산,37,5279.35,5296.67,17.31999999999971,A,A,False,,
|
||||
대산,38,5296.67,5316.67,20,RP-1,RP-1,False,,"대인용
|
||||
연결통로 접속부"
|
||||
대산,39,5316.67,5355.35,38.68000000000029,A,A,False,,
|
||||
대산,40,5355.35,5362.35,7,B-1,B-1,False,,
|
||||
대산,41,5362.35,5376.35,14,B-2,B-2,False,,
|
||||
대산,42,5376.35,5382.35,6,WL-2,WL-2,False,상·하 반단면,
|
||||
대산,43,5382.35,5388.35,6,C-1,C-1,False,전단면,
|
||||
대산,44,5388.35,5398.35,10,C-2,C-2,False,,
|
||||
대산,45,5398.35,5445.35,47,D-1,NP-4,False,"상·하
|
||||
반단면",
|
||||
대산,46,5445.35,5458.35,13,WL-2,NP-5,False,,
|
||||
대산,47,5458.35,5464.35,6,P-2,NP-6,False,,
|
||||
대산,48,5464.35,5491.29,26.9399999999996,P-3,NP-6,True,,
|
||||
대산,49,5491.29,5496.29,5,개착부,NP-6,True,,개착부
|
||||
|
@@ -0,0 +1,48 @@
|
||||
direction,source_row,sta_from,sta_to,length_m,lining_pattern,excavation,excavation_inherited,method,remark
|
||||
당진,55,4740.0,4745.0,5,개착부,,True,,개착부
|
||||
당진,56,4745.0,4763.0,18,P-3,NP-6,False,"상·하
|
||||
반단면",
|
||||
당진,57,4763.0,4785.0,22,WL-2,NP-5,False,,
|
||||
당진,58,4785.0,4796.0,11,WL-1,NP-5,True,,
|
||||
당진,59,4796.0,4839.0,43,D-2,NP-4,False,,
|
||||
당진,60,4839.0,4848.0,9,D-1,NP-4,True,,
|
||||
당진,61,4848.0,4857.0,9,C-2,NP-4,True,전단면,
|
||||
당진,62,4857.0,4862.0,5,C-1,NP-4,True,,
|
||||
당진,63,4862.0,4867.0,5,B-2,B-2,False,,
|
||||
당진,64,4867.0,4873.0,6,B-1,B-1,False,,
|
||||
당진,65,4873.0,4907.0,34,A,A,False,,
|
||||
당진,66,4907.0,4915.0,8,B-1,B-1,False,,
|
||||
당진,67,4915.0,4920.0,5,B-2,B-2,False,,
|
||||
당진,68,4920.0,4940.0,20,RP-1,B-2,True,,"대인용
|
||||
연결통로 접속부"
|
||||
당진,69,4940.0,4988.0,48,C-1,C-1,False,,
|
||||
당진,70,4988.0,4998.0,10,B-2,B-2,False,,
|
||||
당진,71,4998.0,5006.0,8,B-1,B-1,False,,
|
||||
당진,72,5006.0,5019.0,13,B-2,B-2,False,,
|
||||
당진,73,5019.0,5025.0,6,B-1,B-1,False,,
|
||||
당진,74,5025.0,5110.0,85,A,A,False,,
|
||||
당진,75,5110.0,5130.0,20,RP-1,A,True,,"차량용
|
||||
연결통로 접속부"
|
||||
당진,76,5130.0,5140.0,10,B-1,B-1,False,,
|
||||
당진,77,5140.0,5157.0,17,B-2,B-2,False,,
|
||||
당진,78,5157.0,5204.0,47,C-1,C-1,False,,
|
||||
당진,79,5204.0,5211.0,7,B-2,B-2,False,,
|
||||
당진,80,5211.0,5221.0,10,B-1,B-1,False,,
|
||||
당진,81,5221.0,5259.0,38,A,A,False,,
|
||||
당진,82,5259.0,5279.0,20,B-1,B-1,False,,
|
||||
당진,83,5279.0,5300.0,21,A,A,False,,
|
||||
당진,84,5300.0,5320.0,20,RP-1,A,True,,"대인용
|
||||
연결통로 접속부"
|
||||
당진,85,5320.0,5357.0,37,A,A,False,,
|
||||
당진,86,5357.0,5363.0,6,B-1,B-1,False,,
|
||||
당진,87,5363.0,5377.0,14,B-2,B-2,False,,
|
||||
당진,88,5377.0,5390.0,13,C-1,C-1,False,,
|
||||
당진,89,5390.0,5399.0,9,C-2,C-2,False,,
|
||||
당진,90,5399.0,5406.0,7,D-1,NP-4,False,"상·하
|
||||
반단면",
|
||||
당진,91,5406.0,5427.0,21,D-2,NP-4,True,,
|
||||
당진,92,5427.0,5446.0,19,D-1,NP-4,True,,
|
||||
당진,93,5446.0,5462.0,16,WL-2,NP-5,False,,
|
||||
당진,94,5462.0,5468.0,6,P-2,NP-6,False,,
|
||||
당진,95,5468.0,5495.0,27,P-3,NP-6,True,,
|
||||
당진,96,5495.0,5500.0,5,개착부,NP-6,True,,개착부
|
||||
|
@@ -0,0 +1,369 @@
|
||||
seg_from,seg_to,pattern,status
|
||||
4745,4747,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4747,4749,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4749,4751,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4751,4753,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4753,4755,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4755,4757,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4757,4759,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4759,4761,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4761,4763,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4763,4765,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4765,4767,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4767,4769,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4769,4771,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4771,4773,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4773,4775,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4775,4777,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4777,4779,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4779,4781,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4781,4783,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4783,4785,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4785,4787,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4787,4789,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4789,4791,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4791,4793,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4793,4795,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4795,4797,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4797,4799,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4799,4801,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4801,4803,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4803,4805,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4805,4807,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4807,4809,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4809,4811,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4811,4813,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4813,4815,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4815,4817,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4817,4819,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4819,4821,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4821,4823,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4823,4825,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4825,4827,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4827,4829,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4829,4831,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4831,4833,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4833,4835,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4835,4837,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4837,4839,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4839,4841,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4841,4843,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4843,4845,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4845,4847,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4847,4849,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4849,4851,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4851,4853,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4853,4855,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4855,4857,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4857,4859,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4859,4861,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4861,4863,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4863,4865,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4865,4867,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4867,4869,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4869,4871,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4871,4873,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4873,4875,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4875,4877,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4877,4879,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4879,4881,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4881,4883,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4883,4885,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4885,4887,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4887,4889,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4889,4891,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4891,4893,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4893,4895,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4895,4897,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4897,4899,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4899,4901,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4901,4903,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4903,4905,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4905,4907,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4907,4909,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4909,4911,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4911,4913,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4913,4915,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4915,4917,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4917,4919,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4919,4921,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4921,4923,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4923,4925,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4925,4927,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4927,4929,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4929,4931,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4931,4933,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4933,4935,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4935,4937,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4937,4939,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4939,4941,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4941,4943,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4943,4945,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4945,4947,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4947,4949,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4949,4951,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4951,4953,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4953,4955,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4955,4957,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4957,4959,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4959,4961,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4961,4963,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4963,4965,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4965,4967,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4967,4969,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4969,4971,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4971,4973,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4973,4975,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4975,4977,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4977,4979,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4979,4981,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4981,4983,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4983,4985,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4985,4987,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4987,4989,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4989,4991,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4991,4993,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4993,4995,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4995,4997,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4997,4999,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
4999,5001,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5001,5003,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5003,5005,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5005,5007,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5007,5009,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5009,5011,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5011,5013,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5013,5015,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5015,5017,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5017,5019,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5019,5021,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5021,5023,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5023,5025,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5025,5027,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5027,5029,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5029,5031,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5031,5033,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5033,5035,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5035,5037,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5037,5039,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5039,5041,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5041,5043,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5043,5045,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5045,5047,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5047,5049,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5049,5051,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5051,5053,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5053,5055,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5055,5057,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5057,5059,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5059,5061,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5061,5063,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5063,5065,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5065,5067,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5067,5069,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5069,5071,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5071,5073,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5073,5075,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5075,5077,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5077,5079,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5079,5081,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5081,5083,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5083,5085,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5085,5087,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5087,5089,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5089,5091,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5091,5093,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5093,5095,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5095,5097,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5097,5099,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5099,5101,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5101,5103,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5103,5105,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5105,5107,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5107,5109,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5109,5111,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5111,5113,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5113,5115,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5115,5117,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5117,5119,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5119,5121,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5121,5123,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5123,5125,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5125,5127,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5127,5129,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5129,5131,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5131,5133,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5133,5135,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5135,5137,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5137,5139,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5139,5141,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5141,5143,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5143,5145,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5145,5147,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5147,5149,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5149,5151,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5151,5153,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5153,5155,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5155,5157,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5157,5159,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5159,5161,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5161,5163,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5163,5165,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5165,5167,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5167,5169,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5169,5171,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5171,5173,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5173,5175,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5175,5177,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5177,5179,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5179,5181,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5181,5183,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5183,5185,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5185,5187,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5187,5189,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5189,5191,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5191,5193,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5193,5195,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5195,5197,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5197,5199,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5199,5201,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5201,5203,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5203,5205,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5205,5207,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5207,5209,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5209,5211,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5211,5213,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5213,5215,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5215,5217,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5217,5219,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5219,5221,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5221,5223,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5223,5225,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5225,5227,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5227,5229,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5229,5231,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5231,5233,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5233,5235,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5235,5237,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5237,5239,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5239,5241,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5241,5243,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5243,5245,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5245,5247,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5247,5249,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5249,5251,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5251,5253,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5253,5255,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5255,5257,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5257,5259,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5259,5261,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5261,5263,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5263,5265,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5265,5267,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5267,5269,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5269,5271,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5271,5273,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5273,5275,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5275,5277,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5277,5279,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5279,5281,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5281,5283,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5283,5285,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5285,5287,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5287,5289,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5289,5291,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5291,5293,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5293,5295,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5295,5297,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5297,5299,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5299,5301,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5301,5303,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5303,5305,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5305,5307,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5307,5309,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5309,5311,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5311,5313,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5313,5315,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5315,5317,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5317,5319,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5319,5321,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5321,5323,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5323,5325,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5325,5327,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5327,5329,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5329,5331,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5331,5333,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5333,5335,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5335,5337,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5337,5339,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5339,5341,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5341,5343,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5343,5345,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5345,5347,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5347,5349,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5349,5351,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5351,5353,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5353,5355,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5355,5357,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5357,5359,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5359,5361,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5361,5363,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5363,5365,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5365,5367,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5367,5369,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5369,5371,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5371,5373,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5373,5375,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5375,5377,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5377,5379,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5379,5381,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5381,5383,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5383,5385,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5385,5387,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5387,5389,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5389,5391,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5391,5393,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5393,5395,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5395,5397,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5397,5399,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5399,5401,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5401,5403,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5403,5405,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5405,5407,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5407,5409,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5409,5411,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5411,5413,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5413,5415,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5415,5417,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5417,5419,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5419,5421,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5421,5423,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5423,5425,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5425,5427,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5427,5429,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5429,5431,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5431,5433,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5433,5435,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5435,5437,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5437,5439,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5439,5441,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5441,5443,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5443,5445,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5445,5447,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5447,5449,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5449,5451,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5451,5453,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5453,5455,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5455,5457,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5457,5459,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5459,5461,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5461,5463,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5463,5465,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5465,5467,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5467,5469,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5469,5471,NP-5AE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5471,5473,NP-5BE-1-WL-4,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5473,5475,NP-6A,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5475,5477,NP-6B,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5477,5479,NP-4AD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
5479,5480,NP-4BD-1-D-2,"임의 배정 (placeholder, 실제 구간 아님)"
|
||||
|
@@ -0,0 +1,77 @@
|
||||
station_anchor,est_station,across_m,value,x,y
|
||||
4740,4738.3,-32.81,65.969,150794.75,483555.28
|
||||
4740,4738.3,-16.41,50.495,150809.32,483562.8
|
||||
4760,4758.4,-31.48,70.818,150805.68,483536.98
|
||||
4760,4758.4,-16.41,50.993,150819.07,483543.89
|
||||
4780,4778.4,-32.81,72.725,150814.25,483517.47
|
||||
4780,4778.4,-15.97,51.492,150829.22,483525.19
|
||||
4800,4798.5,-31.48,82.17,150825.19,483499.17
|
||||
4800,4798.5,-15.53,51.99,150839.37,483506.48
|
||||
4820,4818.5,-32.81,92.372,150833.76,483479.65
|
||||
4820,4818.5,-16.86,52.488,150847.94,483486.97
|
||||
4840,4838.6,-33.7,100.775,150842.73,483460.34
|
||||
4840,4838.6,-16.86,52.986,150857.69,483468.06
|
||||
4860,4858.6,-33.26,109.7,150852.87,483441.63
|
||||
4860,4858.6,-16.86,53.485,150867.45,483449.15
|
||||
4880,4878.6,-32.81,119.9,150863.02,483422.93
|
||||
4880,4878.6,-16.86,53.983,150877.2,483430.25
|
||||
4900,4898.7,-33.26,130.6,150872.38,483403.82
|
||||
4900,4898.7,-15.97,54.481,150887.74,483411.74
|
||||
4920,4918.7,-31.93,141.1,150883.32,483385.52
|
||||
4920,4918.7,-16.41,54.98,150897.1,483392.63
|
||||
4940,4938.8,-34.59,152.277,150890.71,483365.4
|
||||
4940,4938.8,-16.86,55.478,150906.46,483373.52
|
||||
4960,4958.8,-34.59,155.393,150900.46,483346.49
|
||||
4960,4958.8,-16.86,55.976,150916.22,483354.62
|
||||
4980,4978.9,-34.14,149.607,150910.61,483327.79
|
||||
4980,4978.9,-16.86,56.474,150925.97,483335.71
|
||||
5000,4998.9,-33.7,144.2,150920.75,483309.08
|
||||
5000,4998.9,-16.86,56.973,150935.72,483316.8
|
||||
5020,5019.0,-34.59,145.422,150929.72,483289.77
|
||||
5020,5019.0,-15.97,57.471,150946.26,483298.3
|
||||
5040,5039.0,-34.59,142.999,150939.47,483270.86
|
||||
5040,5039.0,-16.86,57.969,150955.23,483278.99
|
||||
5060,5059.0,-34.14,142.606,150949.62,483252.16
|
||||
5060,5059.0,-16.86,58.467,150964.98,483260.08
|
||||
5080,5079.1,-34.59,142.244,150958.98,483233.05
|
||||
5080,5079.1,-16.86,58.966,150974.74,483241.18
|
||||
5100,5099.1,-34.59,145.737,150968.73,483214.14
|
||||
5100,5099.1,-16.86,59.464,150984.49,483222.27
|
||||
5120,5119.2,-32.81,148.1,150980.06,483196.05
|
||||
5120,5119.2,-16.86,59.962,150994.24,483203.36
|
||||
5140,5139.2,-33.26,146.041,150989.42,483176.94
|
||||
5140,5139.2,-15.97,60.46,151004.79,483184.86
|
||||
5160,5159.3,-32.81,146.118,150999.57,483158.23
|
||||
5160,5159.3,-16.41,60.959,151014.15,483165.75
|
||||
5180,5179.3,-34.59,147.947,151007.75,483138.51
|
||||
5180,5179.3,-15.97,61.457,151024.29,483147.05
|
||||
5200,5199.4,-33.7,152.8,151018.29,483120.01
|
||||
5200,5199.4,-15.97,61.955,151034.05,483128.14
|
||||
5220,5219.4,-33.7,157.214,151028.04,483101.1
|
||||
5220,5219.4,-16.86,62.453,151043.01,483108.83
|
||||
5240,5239.5,-33.7,155.7,151037.8,483082.2
|
||||
5240,5239.5,-16.86,62.952,151052.77,483089.92
|
||||
5260,5259.5,-34.59,146.424,151046.76,483062.88
|
||||
5260,5259.5,-16.41,63.45,151062.91,483071.22
|
||||
5280,5279.5,-33.7,138.6,151057.3,483044.38
|
||||
5280,5279.5,-16.86,63.948,151072.27,483052.11
|
||||
5300,5299.6,-33.7,140.039,151067.06,483025.48
|
||||
5300,5299.6,-16.86,64.446,151082.03,483033.2
|
||||
5320,5319.6,-34.14,130.368,151076.42,483006.37
|
||||
5320,5319.6,-16.86,64.933,151091.78,483014.29
|
||||
5340,5339.7,-33.26,117.65,151086.96,482987.87
|
||||
5340,5339.7,-16.86,65.378,151101.53,482995.38
|
||||
5360,5359.7,-33.26,107.3,151096.71,482968.96
|
||||
5360,5359.7,-15.97,65.781,151112.07,482976.88
|
||||
5380,5379.8,-34.14,103.593,151105.68,482949.65
|
||||
5380,5379.8,-15.97,66.142,151121.83,482957.98
|
||||
5400,5399.8,-33.7,109.032,151115.83,482930.94
|
||||
5400,5399.8,-16.41,66.46,151131.19,482938.87
|
||||
5420,5419.9,-32.81,108.108,151126.37,482912.44
|
||||
5420,5419.9,-16.86,66.737,151140.55,482919.76
|
||||
5440,5439.9,-31.93,98.183,151136.91,482893.94
|
||||
5440,5439.9,-16.41,66.97,151150.7,482901.05
|
||||
5460,5460.0,-31.93,88.124,151146.66,482875.03
|
||||
5460,5460.0,-15.97,67.162,151160.84,482882.35
|
||||
5480,5480.0,-30.6,81.014,151157.6,482856.74
|
||||
5480,5480.0,-15.08,67.311,151171.38,482863.85
|
||||
|
@@ -0,0 +1,36 @@
|
||||
27400,표준 단면도
|
||||
24350,표준단면 적용구간
|
||||
23550,"대 산 1 , 2 터 널"
|
||||
23000,구 분,편 경 사,적 용 구 간 ( 개 착 구 간 제 외 )
|
||||
22550,대산방향,-2%%%,STA.3+070.00 ~ 3+555.00
|
||||
22200,대산 1터널,-2%%% ~ +3%%%,STA.3+070.00 ~ 3+145.00
|
||||
22000,당진방향
|
||||
21850,+3%%%,STA.3+145.00 ~ 3+565.00
|
||||
21500,+2%%%,STA.4+740.85 ~ 5+075.00
|
||||
21150,대산방향,+2%%% ~ -2%%%,STA.5+075.00 ~ 5+135.00
|
||||
20800,-2%%%,STA.5+135.00 ~ 5+476.31
|
||||
20600,대산 2터널
|
||||
20450,-2%%%,STA.4+745.00 ~ 5+105.00
|
||||
20100,-2%%% ~ +2%%%,STA.5+105.00 ~ 5+165.00
|
||||
19900,당진방향
|
||||
19750,+2%%%,STA.5+165.00 ~ 5+480.00
|
||||
18650,"상세 ""A""","상세 ""B"""
|
||||
11500,치 수 표
|
||||
10850,치 수 표
|
||||
10400,S
|
||||
10300,a,b,c,d,e,f,R1,R2,R3,R2',R3',φ1,φ2,φ1',φ2'
|
||||
10200,(%)
|
||||
9850,NOTE
|
||||
9750,+3,2.504,2.354,2.166,1.978,1.500,1.776,"6,982","3,712","4,454","3,910","4,699",59.2860°,67.9676°,52.1470°,60.6263°
|
||||
9300,+2,2.504,2.354,2.166,1.978,1.524,1.759,"6,982","3,712","4,454","3,910","4,699",58.5355°,67.2997°,52.8374°,61.2268°
|
||||
8800,0,2.504,2.354,2.166,1.978,1.570,1.722,"6,982","3,712","4,454","3,910","4,699",57.0441°,65.9756°,54.2241°,62.4352°
|
||||
8300,-2,2.504,2.354,2.166,1.978,1.614,1.683,"6,982","3,712","4,454","3,910","4,699",55.5645°,64.6664°,55.6193°,63.6543°
|
||||
7800,-3,2.504,2.354,2.166,1.978,1.635,1.662,"6,982","3,712","4,454","3,910","4,699",54.8288°,64.0169°,56.3204°,64.2681°
|
||||
6700,S
|
||||
6550,A,B,C,D,E,F,G,H,I,J,K,라이닝 두께
|
||||
6450,(%)
|
||||
6050,+3,6.615,1.735,1.451,1.608,1.293,0.000,-0.430,0.509,0.785,-0.638,-0.362,300
|
||||
5550,+2,6.615,1.735,1.451,1.608,1.293,0.000,-0.430,0.555,0.739,-0.592,-0.408,300
|
||||
5050,0,6.615,1.735,1.451,1.608,1.293,0.000,-0.430,0.647,0.647,-0.500,-0.500,300
|
||||
4600,-2,6.615,1.735,1.451,1.608,1.293,0.000,-0.430,0.739,0.555,-0.408,-0.592,300
|
||||
4100,-3,6.615,1.735,1.451,1.608,1.293,0.000,-0.430,0.785,0.509,-0.362,-0.638,300
|
||||
|
@@ -0,0 +1,656 @@
|
||||
{
|
||||
"1|4745.0005": {
|
||||
"LIN_arcs": {
|
||||
"ref_to_model_max": 99.05839911556747,
|
||||
"ref_to_model_mean": 12.265421914456372,
|
||||
"model_to_ref_max": 2699.228422004956,
|
||||
"model_to_ref_mean": 139.42217237180967,
|
||||
"n_ref": 66562,
|
||||
"n_model": 2539,
|
||||
"clipped_below_model_extent": {
|
||||
"L": 427,
|
||||
"R": 438,
|
||||
"yrange_L": [
|
||||
15264,
|
||||
15659
|
||||
],
|
||||
"yrange_R": [
|
||||
15080,
|
||||
15488
|
||||
]
|
||||
}
|
||||
},
|
||||
"LIN_footlines": {
|
||||
"ref_to_model_max": 199.81630323321525,
|
||||
"ref_to_model_mean": 31.495823939407906,
|
||||
"model_to_ref_max": 8330.781218615124,
|
||||
"model_to_ref_mean": 3658.6932294230833,
|
||||
"n_ref": 2395,
|
||||
"n_model": 2539
|
||||
},
|
||||
"LIN_perarc_ref2model_max_mm": {
|
||||
"LIN_crown_outer": 21.5,
|
||||
"LIN_crown_inner": 17.5,
|
||||
"LIN_haunchL_outer": 492.3,
|
||||
"LIN_haunchL_inner": 12.9,
|
||||
"LIN_haunchR_outer": 461.1,
|
||||
"LIN_haunchR_inner": 15.5
|
||||
},
|
||||
"EXC_arcs": {
|
||||
"ref_to_model_max": 49.97333468210581,
|
||||
"ref_to_model_mean": 13.22944506104266,
|
||||
"model_to_ref_max": 139.10210757850402,
|
||||
"model_to_ref_mean": 69.63490871239516,
|
||||
"n_ref": 35303,
|
||||
"n_model": 2271,
|
||||
"clipped_below_model_extent": {
|
||||
"L": 375,
|
||||
"R": 566,
|
||||
"yrange_L": [
|
||||
15264,
|
||||
15612
|
||||
],
|
||||
"yrange_R": [
|
||||
15080,
|
||||
15612
|
||||
]
|
||||
}
|
||||
},
|
||||
"EXC_crown_ref2model_max_mm": 21.8,
|
||||
"PAV": {
|
||||
"ref_to_model_max": 13.6729631110277,
|
||||
"ref_to_model_mean": 10.97265592073033,
|
||||
"model_to_ref_max": 9.766634709250443,
|
||||
"model_to_ref_mean": 9.376172149516487,
|
||||
"n_ref": 18962,
|
||||
"n_model": 952
|
||||
},
|
||||
"BASE_drawn_span": {
|
||||
"ref_to_model_max": 106.89691611711953,
|
||||
"ref_to_model_mean": 46.68072152026932,
|
||||
"model_to_ref_max": 270.29560834777436,
|
||||
"model_to_ref_mean": 47.30323588291019,
|
||||
"n_ref": 18402,
|
||||
"n_model": 934
|
||||
},
|
||||
"BASE_model_extent_x": [
|
||||
7715,
|
||||
20584
|
||||
],
|
||||
"BASE_model_ymin_ymax": [
|
||||
15067,
|
||||
15760
|
||||
],
|
||||
"DRN": {
|
||||
"ref_to_model_max": 14.457886611257248,
|
||||
"ref_to_model_mean": 7.048062010678219,
|
||||
"model_to_ref_max": 148.08739517488308,
|
||||
"model_to_ref_mean": 9.751422269720287,
|
||||
"n_ref": 5820,
|
||||
"n_model": 373
|
||||
},
|
||||
"DRN_perref_ref2model_max_mm": {
|
||||
"DRN_wallL1": 10.4,
|
||||
"DRN_wallL2": 11.2,
|
||||
"DRN_botL": 14.4,
|
||||
"DRN_pipeL": 14.5,
|
||||
"DRN_wallR1": 10.2,
|
||||
"DRN_wallR2": 11.1,
|
||||
"DRN_botR": 14.4,
|
||||
"DRN_pipeR": 14.5
|
||||
},
|
||||
"DRN_model_xrange": [
|
||||
9099,
|
||||
19149
|
||||
],
|
||||
"DRN_model_yrange": [
|
||||
14670,
|
||||
15690
|
||||
]
|
||||
},
|
||||
"1|5100.0": {
|
||||
"LIN_arcs": {
|
||||
"ref_to_model_max": 98.98374727460279,
|
||||
"ref_to_model_mean": 12.286715807049747,
|
||||
"model_to_ref_max": 2699.208225118645,
|
||||
"model_to_ref_mean": 139.4446947488841,
|
||||
"n_ref": 66562,
|
||||
"n_model": 2539,
|
||||
"clipped_below_model_extent": {
|
||||
"L": 427,
|
||||
"R": 438,
|
||||
"yrange_L": [
|
||||
15264,
|
||||
15659
|
||||
],
|
||||
"yrange_R": [
|
||||
15080,
|
||||
15488
|
||||
]
|
||||
}
|
||||
},
|
||||
"LIN_footlines": {
|
||||
"ref_to_model_max": 199.75630963156448,
|
||||
"ref_to_model_mean": 31.508170955828962,
|
||||
"model_to_ref_max": 8330.792297541451,
|
||||
"model_to_ref_mean": 3658.67650320109,
|
||||
"n_ref": 2395,
|
||||
"n_model": 2539
|
||||
},
|
||||
"LIN_perarc_ref2model_max_mm": {
|
||||
"LIN_crown_outer": 21.5,
|
||||
"LIN_crown_inner": 17.4,
|
||||
"LIN_haunchL_outer": 492.3,
|
||||
"LIN_haunchL_inner": 12.9,
|
||||
"LIN_haunchR_outer": 461.1,
|
||||
"LIN_haunchR_inner": 15.5
|
||||
},
|
||||
"EXC_arcs": {
|
||||
"ref_to_model_max": 49.90231927152876,
|
||||
"ref_to_model_mean": 13.25015537105612,
|
||||
"model_to_ref_max": 139.12313900485057,
|
||||
"model_to_ref_mean": 69.6577719572834,
|
||||
"n_ref": 35303,
|
||||
"n_model": 2271,
|
||||
"clipped_below_model_extent": {
|
||||
"L": 375,
|
||||
"R": 566,
|
||||
"yrange_L": [
|
||||
15264,
|
||||
15612
|
||||
],
|
||||
"yrange_R": [
|
||||
15080,
|
||||
15612
|
||||
]
|
||||
}
|
||||
},
|
||||
"EXC_crown_ref2model_max_mm": 21.8,
|
||||
"PAV": {
|
||||
"ref_to_model_max": 14.418186911034798,
|
||||
"ref_to_model_mean": 10.996432167000822,
|
||||
"model_to_ref_max": 10.776825902966216,
|
||||
"model_to_ref_mean": 9.399395181664575,
|
||||
"n_ref": 18962,
|
||||
"n_model": 952
|
||||
},
|
||||
"BASE_drawn_span": {
|
||||
"ref_to_model_max": 106.95235216721005,
|
||||
"ref_to_model_mean": 46.704840227446,
|
||||
"model_to_ref_max": 270.3072089440827,
|
||||
"model_to_ref_mean": 47.3306581098095,
|
||||
"n_ref": 18402,
|
||||
"n_model": 934
|
||||
},
|
||||
"BASE_model_extent_x": [
|
||||
7715,
|
||||
20583
|
||||
],
|
||||
"BASE_model_ymin_ymax": [
|
||||
15067,
|
||||
15759
|
||||
],
|
||||
"DRN": {
|
||||
"ref_to_model_max": 14.490884181941974,
|
||||
"ref_to_model_mean": 7.0658785237396495,
|
||||
"model_to_ref_max": 148.0563566649249,
|
||||
"model_to_ref_mean": 9.77795072083027,
|
||||
"n_ref": 5820,
|
||||
"n_model": 373
|
||||
},
|
||||
"DRN_perref_ref2model_max_mm": {
|
||||
"DRN_wallL1": 10.4,
|
||||
"DRN_wallL2": 11.3,
|
||||
"DRN_botL": 14.4,
|
||||
"DRN_pipeL": 14.5,
|
||||
"DRN_wallR1": 10.2,
|
||||
"DRN_wallR2": 11.2,
|
||||
"DRN_botR": 14.4,
|
||||
"DRN_pipeR": 14.4
|
||||
},
|
||||
"DRN_model_xrange": [
|
||||
9099,
|
||||
19149
|
||||
],
|
||||
"DRN_model_yrange": [
|
||||
14670,
|
||||
15690
|
||||
]
|
||||
},
|
||||
"1|5469.9995": {
|
||||
"LIN_arcs": {
|
||||
"ref_to_model_max": 99.05839911694011,
|
||||
"ref_to_model_mean": 12.265421913408609,
|
||||
"model_to_ref_max": 2699.2284220029933,
|
||||
"model_to_ref_mean": 139.55032916231897,
|
||||
"n_ref": 66562,
|
||||
"n_model": 2540,
|
||||
"clipped_below_model_extent": {
|
||||
"L": 427,
|
||||
"R": 438,
|
||||
"yrange_L": [
|
||||
15264,
|
||||
15659
|
||||
],
|
||||
"yrange_R": [
|
||||
15080,
|
||||
15488
|
||||
]
|
||||
}
|
||||
},
|
||||
"LIN_footlines": {
|
||||
"ref_to_model_max": 199.8163032523252,
|
||||
"ref_to_model_mean": 31.495823938300035,
|
||||
"model_to_ref_max": 8330.781218614657,
|
||||
"model_to_ref_mean": 3657.3796663221337,
|
||||
"n_ref": 2395,
|
||||
"n_model": 2540
|
||||
},
|
||||
"LIN_perarc_ref2model_max_mm": {
|
||||
"LIN_crown_outer": 21.5,
|
||||
"LIN_crown_inner": 17.5,
|
||||
"LIN_haunchL_outer": 492.3,
|
||||
"LIN_haunchL_inner": 12.9,
|
||||
"LIN_haunchR_outer": 461.1,
|
||||
"LIN_haunchR_inner": 15.5
|
||||
},
|
||||
"EXC_arcs": {
|
||||
"ref_to_model_max": 49.973334683068344,
|
||||
"ref_to_model_mean": 13.229445060405267,
|
||||
"model_to_ref_max": 139.10210757277747,
|
||||
"model_to_ref_mean": 69.63490871114456,
|
||||
"n_ref": 35303,
|
||||
"n_model": 2271,
|
||||
"clipped_below_model_extent": {
|
||||
"L": 375,
|
||||
"R": 566,
|
||||
"yrange_L": [
|
||||
15264,
|
||||
15612
|
||||
],
|
||||
"yrange_R": [
|
||||
15080,
|
||||
15612
|
||||
]
|
||||
}
|
||||
},
|
||||
"EXC_crown_ref2model_max_mm": 21.8,
|
||||
"PAV": {
|
||||
"ref_to_model_max": 193.59943538166334,
|
||||
"ref_to_model_mean": 85.90038197968867,
|
||||
"model_to_ref_max": 193.58867016575806,
|
||||
"model_to_ref_mean": 85.49583497432751,
|
||||
"n_ref": 18962,
|
||||
"n_model": 952
|
||||
},
|
||||
"BASE_drawn_span": {
|
||||
"ref_to_model_max": 194.0339970575608,
|
||||
"ref_to_model_mean": 76.70070967162879,
|
||||
"model_to_ref_max": 270.2956083490425,
|
||||
"model_to_ref_mean": 71.6861767350549,
|
||||
"n_ref": 18402,
|
||||
"n_model": 925
|
||||
},
|
||||
"BASE_model_extent_x": [
|
||||
7715,
|
||||
20584
|
||||
],
|
||||
"BASE_model_ymin_ymax": [
|
||||
15067,
|
||||
15760
|
||||
],
|
||||
"DRN": {
|
||||
"ref_to_model_max": 14.45788661735982,
|
||||
"ref_to_model_mean": 7.048062004823568,
|
||||
"model_to_ref_max": 148.08739519731785,
|
||||
"model_to_ref_mean": 9.751422260123833,
|
||||
"n_ref": 5820,
|
||||
"n_model": 373
|
||||
},
|
||||
"DRN_perref_ref2model_max_mm": {
|
||||
"DRN_wallL1": 10.4,
|
||||
"DRN_wallL2": 11.2,
|
||||
"DRN_botL": 14.4,
|
||||
"DRN_pipeL": 14.5,
|
||||
"DRN_wallR1": 10.2,
|
||||
"DRN_wallR2": 11.1,
|
||||
"DRN_botR": 14.4,
|
||||
"DRN_pipeR": 14.5
|
||||
},
|
||||
"DRN_model_xrange": [
|
||||
9099,
|
||||
19149
|
||||
],
|
||||
"DRN_model_yrange": [
|
||||
14670,
|
||||
15690
|
||||
]
|
||||
},
|
||||
"-1|4745.0005": {
|
||||
"LIN_arcs": {
|
||||
"ref_to_model_max": 165.93842403641,
|
||||
"ref_to_model_mean": 19.342371436798466,
|
||||
"model_to_ref_max": 2694.4903611935015,
|
||||
"model_to_ref_mean": 148.5127579371637,
|
||||
"n_ref": 66566,
|
||||
"n_model": 2539,
|
||||
"clipped_below_model_extent": {
|
||||
"L": 245,
|
||||
"R": 616,
|
||||
"yrange_L": [
|
||||
15264,
|
||||
15488
|
||||
],
|
||||
"yrange_R": [
|
||||
15080,
|
||||
15658
|
||||
]
|
||||
}
|
||||
},
|
||||
"LIN_footlines": {
|
||||
"ref_to_model_max": 278.1041972578194,
|
||||
"ref_to_model_mean": 86.14276227224678,
|
||||
"model_to_ref_max": 8325.718000790263,
|
||||
"model_to_ref_mean": 3664.9850644439653,
|
||||
"n_ref": 2395,
|
||||
"n_model": 2539
|
||||
},
|
||||
"LIN_perarc_ref2model_max_mm": {
|
||||
"LIN_crown_outer": 21.5,
|
||||
"LIN_crown_inner": 17.5,
|
||||
"LIN_haunchL_outer": 278.9,
|
||||
"LIN_haunchL_inner": 79.7,
|
||||
"LIN_haunchR_outer": 676.9,
|
||||
"LIN_haunchR_inner": 165.9
|
||||
},
|
||||
"EXC_arcs": {
|
||||
"ref_to_model_max": 110.0366585359107,
|
||||
"ref_to_model_mean": 20.147468133684814,
|
||||
"model_to_ref_max": 230.36912980232339,
|
||||
"model_to_ref_mean": 77.01959791893609,
|
||||
"n_ref": 35303,
|
||||
"n_model": 2271,
|
||||
"clipped_below_model_extent": {
|
||||
"L": 375,
|
||||
"R": 566,
|
||||
"yrange_L": [
|
||||
15264,
|
||||
15612
|
||||
],
|
||||
"yrange_R": [
|
||||
15080,
|
||||
15612
|
||||
]
|
||||
}
|
||||
},
|
||||
"EXC_crown_ref2model_max_mm": 21.8,
|
||||
"PAV": {
|
||||
"ref_to_model_max": 193.58697559581455,
|
||||
"ref_to_model_mean": 85.90449213019785,
|
||||
"model_to_ref_max": 193.50070137422574,
|
||||
"model_to_ref_mean": 85.49177355782385,
|
||||
"n_ref": 18962,
|
||||
"n_model": 952
|
||||
},
|
||||
"BASE_drawn_span": {
|
||||
"ref_to_model_max": 186.2456896549011,
|
||||
"ref_to_model_mean": 65.25754410234194,
|
||||
"model_to_ref_max": 241.51176893122732,
|
||||
"model_to_ref_mean": 79.35668872937082,
|
||||
"n_ref": 18402,
|
||||
"n_model": 934
|
||||
},
|
||||
"BASE_model_extent_x": [
|
||||
7669,
|
||||
20538
|
||||
],
|
||||
"BASE_model_ymin_ymax": [
|
||||
15067,
|
||||
15760
|
||||
],
|
||||
"DRN": {
|
||||
"ref_to_model_max": 191.09289892056788,
|
||||
"ref_to_model_mean": 43.78263107043471,
|
||||
"model_to_ref_max": 230.1686793175862,
|
||||
"model_to_ref_mean": 53.43022849360874,
|
||||
"n_ref": 5820,
|
||||
"n_model": 373
|
||||
},
|
||||
"DRN_perref_ref2model_max_mm": {
|
||||
"DRN_wallL1": 185.1,
|
||||
"DRN_wallL2": 191.1,
|
||||
"DRN_botL": 142.3,
|
||||
"DRN_pipeL": 149.8,
|
||||
"DRN_wallR1": 174.4,
|
||||
"DRN_wallR2": 174.4,
|
||||
"DRN_botR": 174.7,
|
||||
"DRN_pipeR": 124.6
|
||||
},
|
||||
"DRN_model_xrange": [
|
||||
9103,
|
||||
19153
|
||||
],
|
||||
"DRN_model_yrange": [
|
||||
14670,
|
||||
15690
|
||||
]
|
||||
},
|
||||
"-1|5100.0": {
|
||||
"LIN_arcs": {
|
||||
"ref_to_model_max": 165.86208125776304,
|
||||
"ref_to_model_mean": 19.353584271599697,
|
||||
"model_to_ref_max": 2694.466152098678,
|
||||
"model_to_ref_mean": 148.51959481437646,
|
||||
"n_ref": 66566,
|
||||
"n_model": 2539,
|
||||
"clipped_below_model_extent": {
|
||||
"L": 245,
|
||||
"R": 616,
|
||||
"yrange_L": [
|
||||
15264,
|
||||
15488
|
||||
],
|
||||
"yrange_R": [
|
||||
15080,
|
||||
15658
|
||||
]
|
||||
}
|
||||
},
|
||||
"LIN_footlines": {
|
||||
"ref_to_model_max": 278.0451437760574,
|
||||
"ref_to_model_mean": 86.11198124109171,
|
||||
"model_to_ref_max": 8325.7291267321,
|
||||
"model_to_ref_mean": 3664.9615785773476,
|
||||
"n_ref": 2395,
|
||||
"n_model": 2539
|
||||
},
|
||||
"LIN_perarc_ref2model_max_mm": {
|
||||
"LIN_crown_outer": 21.5,
|
||||
"LIN_crown_inner": 17.4,
|
||||
"LIN_haunchL_outer": 278.9,
|
||||
"LIN_haunchL_inner": 79.6,
|
||||
"LIN_haunchR_outer": 676.9,
|
||||
"LIN_haunchR_inner": 165.9
|
||||
},
|
||||
"EXC_arcs": {
|
||||
"ref_to_model_max": 109.97882644641383,
|
||||
"ref_to_model_mean": 20.162782046328307,
|
||||
"model_to_ref_max": 230.32659770807805,
|
||||
"model_to_ref_mean": 77.03356500761437,
|
||||
"n_ref": 35303,
|
||||
"n_model": 2271,
|
||||
"clipped_below_model_extent": {
|
||||
"L": 375,
|
||||
"R": 566,
|
||||
"yrange_L": [
|
||||
15264,
|
||||
15612
|
||||
],
|
||||
"yrange_R": [
|
||||
15080,
|
||||
15612
|
||||
]
|
||||
}
|
||||
},
|
||||
"EXC_crown_ref2model_max_mm": 21.8,
|
||||
"PAV": {
|
||||
"ref_to_model_max": 192.60836907668372,
|
||||
"ref_to_model_mean": 85.5974876462991,
|
||||
"model_to_ref_max": 192.52054570763823,
|
||||
"model_to_ref_mean": 85.17944998313874,
|
||||
"n_ref": 18962,
|
||||
"n_model": 952
|
||||
},
|
||||
"BASE_drawn_span": {
|
||||
"ref_to_model_max": 185.37579093848115,
|
||||
"ref_to_model_mean": 65.10503094806941,
|
||||
"model_to_ref_max": 241.49813213935548,
|
||||
"model_to_ref_mean": 79.21916715369449,
|
||||
"n_ref": 18402,
|
||||
"n_model": 934
|
||||
},
|
||||
"BASE_model_extent_x": [
|
||||
7669,
|
||||
20538
|
||||
],
|
||||
"BASE_model_ymin_ymax": [
|
||||
15067,
|
||||
15759
|
||||
],
|
||||
"DRN": {
|
||||
"ref_to_model_max": 191.07984589599806,
|
||||
"ref_to_model_mean": 43.782144626512356,
|
||||
"model_to_ref_max": 230.1425275457013,
|
||||
"model_to_ref_mean": 53.43922451031213,
|
||||
"n_ref": 5820,
|
||||
"n_model": 373
|
||||
},
|
||||
"DRN_perref_ref2model_max_mm": {
|
||||
"DRN_wallL1": 185.1,
|
||||
"DRN_wallL2": 191.1,
|
||||
"DRN_botL": 142.3,
|
||||
"DRN_pipeL": 149.8,
|
||||
"DRN_wallR1": 174.4,
|
||||
"DRN_wallR2": 174.4,
|
||||
"DRN_botR": 174.7,
|
||||
"DRN_pipeR": 124.6
|
||||
},
|
||||
"DRN_model_xrange": [
|
||||
9103,
|
||||
19153
|
||||
],
|
||||
"DRN_model_yrange": [
|
||||
14670,
|
||||
15690
|
||||
]
|
||||
},
|
||||
"-1|5469.9995": {
|
||||
"LIN_arcs": {
|
||||
"ref_to_model_max": 165.93842404388747,
|
||||
"ref_to_model_mean": 19.342371439075382,
|
||||
"model_to_ref_max": 2694.4903611915724,
|
||||
"model_to_ref_mean": 148.67066912972504,
|
||||
"n_ref": 66566,
|
||||
"n_model": 2540,
|
||||
"clipped_below_model_extent": {
|
||||
"L": 245,
|
||||
"R": 616,
|
||||
"yrange_L": [
|
||||
15264,
|
||||
15488
|
||||
],
|
||||
"yrange_R": [
|
||||
15080,
|
||||
15658
|
||||
]
|
||||
}
|
||||
},
|
||||
"LIN_footlines": {
|
||||
"ref_to_model_max": 278.10419727094626,
|
||||
"ref_to_model_mean": 86.14276227398764,
|
||||
"model_to_ref_max": 8325.718000790119,
|
||||
"model_to_ref_mean": 3663.701800289308,
|
||||
"n_ref": 2395,
|
||||
"n_model": 2540
|
||||
},
|
||||
"LIN_perarc_ref2model_max_mm": {
|
||||
"LIN_crown_outer": 21.5,
|
||||
"LIN_crown_inner": 17.5,
|
||||
"LIN_haunchL_outer": 278.9,
|
||||
"LIN_haunchL_inner": 79.7,
|
||||
"LIN_haunchR_outer": 676.9,
|
||||
"LIN_haunchR_inner": 165.9
|
||||
},
|
||||
"EXC_arcs": {
|
||||
"ref_to_model_max": 110.03665854392911,
|
||||
"ref_to_model_mean": 20.147468133951584,
|
||||
"model_to_ref_max": 230.36912981558459,
|
||||
"model_to_ref_mean": 77.01959791929141,
|
||||
"n_ref": 35303,
|
||||
"n_model": 2271,
|
||||
"clipped_below_model_extent": {
|
||||
"L": 375,
|
||||
"R": 566,
|
||||
"yrange_L": [
|
||||
15264,
|
||||
15612
|
||||
],
|
||||
"yrange_R": [
|
||||
15080,
|
||||
15612
|
||||
]
|
||||
}
|
||||
},
|
||||
"EXC_crown_ref2model_max_mm": 21.8,
|
||||
"PAV": {
|
||||
"ref_to_model_max": 13.753593831242364,
|
||||
"ref_to_model_mean": 10.921448830959804,
|
||||
"model_to_ref_max": 9.778010359538655,
|
||||
"model_to_ref_mean": 9.31009466082442,
|
||||
"n_ref": 18962,
|
||||
"n_model": 952
|
||||
},
|
||||
"BASE_drawn_span": {
|
||||
"ref_to_model_max": 140.76940169714493,
|
||||
"ref_to_model_mean": 48.22264276831631,
|
||||
"model_to_ref_max": 241.5117689307402,
|
||||
"model_to_ref_mean": 55.15185176197466,
|
||||
"n_ref": 18402,
|
||||
"n_model": 925
|
||||
},
|
||||
"BASE_model_extent_x": [
|
||||
7669,
|
||||
20538
|
||||
],
|
||||
"BASE_model_ymin_ymax": [
|
||||
15067,
|
||||
15760
|
||||
],
|
||||
"DRN": {
|
||||
"ref_to_model_max": 191.09289891938243,
|
||||
"ref_to_model_mean": 43.782631065576105,
|
||||
"model_to_ref_max": 230.16867930371887,
|
||||
"model_to_ref_mean": 53.430228486469154,
|
||||
"n_ref": 5820,
|
||||
"n_model": 373
|
||||
},
|
||||
"DRN_perref_ref2model_max_mm": {
|
||||
"DRN_wallL1": 185.1,
|
||||
"DRN_wallL2": 191.1,
|
||||
"DRN_botL": 142.3,
|
||||
"DRN_pipeL": 149.8,
|
||||
"DRN_wallR1": 174.4,
|
||||
"DRN_wallR2": 174.4,
|
||||
"DRN_botR": 174.7,
|
||||
"DRN_pipeR": 124.6
|
||||
},
|
||||
"DRN_model_xrange": [
|
||||
9103,
|
||||
19153
|
||||
],
|
||||
"DRN_model_yrange": [
|
||||
14670,
|
||||
15690
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# Step 3 판정표 — 콘크리트 라이닝 판독 (Task 6, 대산2본선3D변환 v0.1.00)
|
||||
|
||||
⚠️ DEGRADED: 자기검증 (생성 세션과 판정 세션이 동일. 독립 검증 에이전트 미실행 — V14 FAIL(자기검증), V12 미실시)
|
||||
|
||||
| # | 검증 항목 | 필수 | 판정 | 방법·모집단 | 비고 |
|
||||
|---|---|---|---|---|---|
|
||||
| V1 | 업무 목표 충족 | ★ | PASS(범위 내) | 목표: 라이닝 도면을 읽어 측점 없이 반영 가능한 항목을 모델·뷰어에 연결. 6장 판독, 메타·뷰어 연결 확인 | 측점별 구간 반영은 도면에 측점 없음 → 미실시·한계로 이관 |
|
||||
| V2 | 형태·포맷 일치 | ★ | PASS | DOC .md(변경 이력 표 포함), 메타 JSON, 뷰어 HTML 형식 확인 | — |
|
||||
| V3 | 근거 누락 없음 | | PASS | 공통 사양은 6장 전수에서 확인 (C1) | 시트별 근거는 표에 도면 번호로 기재 |
|
||||
| V4 | 수치·데이터 정합성 | ★ | PASS(전수) | verify_lining.py C1·C3·C4·C5: 6/6 시트 공통값, 패턴군 9 = 보고서 9행, R1 6.982 = 03-001 원본 텍스트, 두께 0.300 m | 최초 실행 3건 FAIL: 검증 스크립트 버그 2건 수정, SD400 누락 1건 보고서 수정 후 19/19 |
|
||||
| V5 | 시각 자료 반영 완전성 | | N/A | 보고서에 도표 없음 | 뷰어 화면은 V11에서 확인 |
|
||||
| V6 | 논리적 비약·모순 없음 | | PASS | 두께 = 외면 R − 내면 R: 6.982 → 0.300 m(일치), 6.892 → 0.390 m(불일치) | R1 충돌은 사용자 결정(6.982 유지)으로 해소, 결정 문구 기록 |
|
||||
| V7 | 사용자 제약 준수 | | PASS | 800 m 범위, 골격 LOD, 패턴 placeholder 표기, 측점 미확정 공백 표시 | — |
|
||||
| V8 | 저장 규칙 준수 | ★ | PASS | DOC/03_설계/ 하위, 파일명 `[유형]_[주제]_[YYYY-MM-DD]_v[버전].md` 형식 | — |
|
||||
| V9 | OUTPUT 실재성 | ★ | PASS | ls 존재, 파일명 버전 v0.1.00 = 헤더 버전, 핵심 항목(fck, SD400, 입출구부, 결정 문구) 본문 존재 (C6·C6b) | 파일명만 확인하지 않고 내용 대조 |
|
||||
| V10 | 수용 기준 충족 | ★ | PASS(사후 기준) | 기준: 6장 전수 판독, 공통 사양 6/6 일치, 패턴군 전수 추출, 충돌 기록 | 착수 시 기준 미기재 → 본 판에서 사후 설정 (R8 위반 한계로 기록) |
|
||||
| V11 | 산출물 가동 가능성 | ★ | PASS | verify_viewer_headless.py: 헤드리스 Chromium, 페이지 오류 0, canvas 존재, 메타 표시, 라이닝 클릭 → 측점 5+202·패턴·라이닝 사양 표시 (25 클릭 중 15번째 적중) | favicon 404 1건은 무해 |
|
||||
| V12 | 원본 무결성 (지문 불변) | | FAIL(미실시) | 지문 기준값을 착수 시 기록하지 않음. 대조 불가 | 재개: 다음 Task 전 RAG 원본 sha256 기록, 이후 대조. 쓰기 코드 없음은 C8로 확인 |
|
||||
| V13 | 검증 스크립트 보관 | | PASS | verify_lining.py, verify_viewer_headless.py 모두 Code/3d-drawing/scripts 보관 | 커밋 2bdda98 |
|
||||
| V14 | 검증 독립성 | ★ | FAIL(자기검증) | 생성과 판정 동일 세션, 독립 에이전트 미실행 | 상단 DEGRADED 배너 기재 |
|
||||
| V15 | 자율성 등급 준수 | ★ | PASS | 대조표 아래 | — |
|
||||
| V16 | 오케스트레이션 규약 | ★ | N/A | 위임 없음 (ORCH-TRIGGER 비해당) | — |
|
||||
| V17 | CAG 정합 | ★ | N/A | 팩 미적재. RAG DXF는 도면이므로 직접 열기 허용 범위 | — |
|
||||
|
||||
**V15 HITL 고정 대조표**
|
||||
|
||||
| 항목 | 해당 여부 | 근거 |
|
||||
|---|---|---|
|
||||
| (1) R5 업무 규정·도메인 규칙 해석 | 해당 | 패턴군 대응 해석, R1 채택 → 사용자 결정 확보 |
|
||||
| (2) F13 원본 데이터 구조 변경 | 아니오 | RAG 원본은 읽기만 함 (C8) |
|
||||
| (3) R7 재작업 상한 도달 | 아니오 | DOC 재생성 2회(결정 반영, SD400 보정). 상한(하루 2회) 이내 |
|
||||
| (4) v1.0.00 이상 대외 발행 최종 확정 | 아니오 | v0.1.00 |
|
||||
| (5) F5 MINOR·MAJOR 승급 | 아니오 | PATCH 단위 |
|
||||
|
||||
게이트 기록: 판정표 위치 = 본 파일. 검증 출력 = data/verify_lining_output.txt.
|
||||
|
||||
## 재검증 (수정 후, 2026-10-21)
|
||||
|
||||
사용자 "수정 요청"에 따라 2 m placeholder를 엑셀 `참고_지보패턴현황`의 당진방향 측점표(4+740~5+500, 42행)로 교체했다. 적용 결과 판정:
|
||||
|
||||
| # | 검증 항목 | 판정 | 방법·모집단 | 비고 |
|
||||
|---|---|---|---|---|
|
||||
| V4 | 측점표 정합 | PASS(독립, 전수) | 독립 에이전트가 원본 xlsx에서 재추출: 42행 연속, 길이 42/42, 모델 pattern 42/42 일치, 735/735 측점 커버 | C5 literal FAIL: 개착부는 모델 범위(4+745~5+480) 밖, 범위 내 12종 전부 일치 |
|
||||
| V4b | 기하 색상 디코딩 | PASS(독립, 전수 735 밴드·736 링) | GLB COLOR_0 디코딩 후 측점별 패턴 대조 | 패턴 경계 39곳은 두 색의 중간색(보간)으로 정확 색 매칭 불가. 한계로 기록 |
|
||||
| V14 | 검증 독립성 | PARTIAL | 측점표·색상 대조는 독립 에이전트 수행. 라이닝 도면 판독 DOC는 자기검증 | 라이닝 DOC 부분은 여전히 FAIL(자기검증) |
|
||||
| V1 | 업무 목표 | PASS(범위 내) | 측점 매핑 적용 완료. 도면 E-1, E-2, RP-2, WL-3은 두 표에 없어 [미확정] | 사용자 결정 2026-10-21 |
|
||||
| V7 | 사용자 제약 | PASS | 대산 WL-4는 참고만 (사용자 결정) | — |
|
||||
|
||||
재검증 자동 검사: verify_lining.py 결과는 data/verify_lining_output.txt 참조. V12(원본 지문)는 여전히 미실시.
|
||||
@@ -0,0 +1,20 @@
|
||||
[PASS] C1 fck 27 in all sheets: 6/6 sheets (표본 아님, 전수)
|
||||
[PASS] C1 SD400 in all sheets: 6/6 sheets (표본 아님, 전수)
|
||||
[PASS] C1 Ø25 in all sheets: 6/6 sheets (표본 아님, 전수)
|
||||
[PASS] C1 R1 6.892 in all sheets: 6/6 sheets (표본 아님, 전수)
|
||||
[PASS] C1 R2 3,712 in all sheets: 6/6 sheets (표본 아님, 전수)
|
||||
[PASS] C1 R2' 3,910 in all sheets: 6/6 sheets (표본 아님, 전수)
|
||||
[PASS] C1 EL -0.367 in all sheets: 6/6 sheets (표본 아님, 전수)
|
||||
[PASS] C1 EL 0.000 in all sheets: 6/6 sheets (표본 아님, 전수)
|
||||
[PASS] C2 온도미고려 detail sheets: 3 sheets: ['005', '006', '007'] (전수)
|
||||
[PASS] C3 pattern-detail count = report rows: raw 9 vs report 9 (전수)
|
||||
[PASS] C4 R1 6.982 in standard section (03-001 raw): 03-001 raw texts scanned: 322
|
||||
[PASS] C4b standard table DOC states R1 6,982: 설계_표준단면치수표 본문 대조
|
||||
[PASS] C4c report records decision: R1 결정 문구 존재
|
||||
[PASS] C5 thickness arithmetic: 7.282-6.982=0.3 m (matches 300 mm), 7.282-6.892=0.39 m (does not match)
|
||||
[PASS] C6 file name pattern and version: name v0.1.00 vs header v0.1.00
|
||||
[PASS] C6b OUTPUT key items present: fck, SD400, 입출구부, 결정 문구
|
||||
[PASS] C7 meta lining_reference 9 groups: 9 groups, status=C0060304-004~009 | fck 27 MPa | SD400 | ...
|
||||
[PASS] C7b meta open records decision: open[] 결정 문구
|
||||
[PASS] C8 no script writes into RAG: scanned 4 scripts
|
||||
SUMMARY: 19 PASS / 0 FAIL of 19
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"inner_radius_max_dev_m": 8.881784197001252e-16,
|
||||
"lining_thickness_crown_m": 0.3,
|
||||
"crown_above_road_m (expect 6.615 at S=0 station 5000)": 6.615,
|
||||
"road_z_4745_m": 50.62,
|
||||
"road_z_5480_m": 67.311,
|
||||
"plan_chord_length_total_m (4+700..5+500)": 799.905,
|
||||
"station_range_modelled": "4745 .. 5480 (ring step 1.0 m, 736 rings)",
|
||||
"pattern_zones (지보패턴표 당진방향)": 42,
|
||||
"pattern_coverage_all_stations": true,
|
||||
"pattern_counts": {
|
||||
"A": 5,
|
||||
"B-1": 8,
|
||||
"B-2": 7,
|
||||
"C-1": 4,
|
||||
"C-2": 2,
|
||||
"D-1": 3,
|
||||
"D-2": 2,
|
||||
"P-2": 1,
|
||||
"P-3": 2,
|
||||
"RP-1": 3,
|
||||
"WL-1": 1,
|
||||
"WL-2": 2,
|
||||
"개착부": 2
|
||||
},
|
||||
"glb_bytes": 2201764,
|
||||
"glb_reload_geometries": 5
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"solid_count": 320,
|
||||
"runs": 40,
|
||||
"pattern_coverage": "4745..5480 contiguous",
|
||||
"lining_runs": 40,
|
||||
"excavation_runs": 40,
|
||||
"volume_sum_m3": 12406.138,
|
||||
"step_bytes": 91713125,
|
||||
"glb_bytes": 1566612,
|
||||
"glb_geometries": 326,
|
||||
"section_overlap_m2_by_station": {
|
||||
"4745": {
|
||||
"lining_vs_excavation": 0.01732111,
|
||||
"base_vs_pavement": 0.0,
|
||||
"lining_vs_base": 0.0,
|
||||
"pavement_vs_lining": 0.0,
|
||||
"base_vs_drainL": 0.0,
|
||||
"base_vs_drainR": 0.0
|
||||
},
|
||||
"5105": {
|
||||
"lining_vs_excavation": 0.01732111,
|
||||
"base_vs_pavement": 0.0,
|
||||
"lining_vs_base": 0.0,
|
||||
"pavement_vs_lining": 0.0,
|
||||
"base_vs_drainL": 0.0,
|
||||
"base_vs_drainR": 0.0
|
||||
},
|
||||
"5165": {
|
||||
"lining_vs_excavation": 0.01732111,
|
||||
"base_vs_pavement": 0.0,
|
||||
"lining_vs_base": 0.0,
|
||||
"pavement_vs_lining": 0.0,
|
||||
"base_vs_drainL": 0.0,
|
||||
"base_vs_drainR": 0.0
|
||||
},
|
||||
"5480": {
|
||||
"lining_vs_excavation": 0.01732111,
|
||||
"base_vs_pavement": 0.0,
|
||||
"lining_vs_base": 0.0,
|
||||
"pavement_vs_lining": 0.0,
|
||||
"base_vs_drainL": 0.0,
|
||||
"base_vs_drainR": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,326 @@
|
||||
{
|
||||
"model": "tunnel_skeleton.glb",
|
||||
"scope": "4745~5480 (4+745~5+480)",
|
||||
"units": "m",
|
||||
"origin_plan_xy": [
|
||||
151090.911,
|
||||
483688.46
|
||||
],
|
||||
"sources": {
|
||||
"centreline": "C0060302-001 CA-ALGN-STSM (side-tick midpoint)",
|
||||
"road_grade_B": "C0060302-002 CR-TABL-TEX2 (계획고=노면)",
|
||||
"GL_A": "C0060302-002 CR-TABL-TEX2 (지반고)",
|
||||
"section": "C0060303-001 CS-TABL-TEX1/2, CS-CONC-MAJR, CS-EXCV"
|
||||
},
|
||||
"approximations": [
|
||||
"walls vertical down to road; invert not modelled",
|
||||
"lining band thickness at walls is radial-consistent but not as-built",
|
||||
"centreline and grade linearly interpolated between nodes",
|
||||
"superelevation sign convention assumed (z += S/100 * x, x = left of tangent)"
|
||||
],
|
||||
"layers": [
|
||||
"lining_outer_R7282",
|
||||
"lining_inner_R6982",
|
||||
"excavation_R7402",
|
||||
"road_surface_계획고",
|
||||
"ground_GL_지반고"
|
||||
],
|
||||
"ring_step_m": 1.0,
|
||||
"ring_points": 43,
|
||||
"first_station": 4745,
|
||||
"pattern_status": "지보패턴: 엑셀 참고_지보패턴현황 당진방향 표(4+740~5+500, 42행, 빈틈 없음) 적용. 출처 data/pattern_stations_dangjin.csv (사용자 결정 2026-10-21)",
|
||||
"patterns": [
|
||||
{
|
||||
"from": 4740,
|
||||
"to": 4745,
|
||||
"pattern": "개착부"
|
||||
},
|
||||
{
|
||||
"from": 4745,
|
||||
"to": 4763,
|
||||
"pattern": "P-3"
|
||||
},
|
||||
{
|
||||
"from": 4763,
|
||||
"to": 4785,
|
||||
"pattern": "WL-2"
|
||||
},
|
||||
{
|
||||
"from": 4785,
|
||||
"to": 4796,
|
||||
"pattern": "WL-1"
|
||||
},
|
||||
{
|
||||
"from": 4796,
|
||||
"to": 4839,
|
||||
"pattern": "D-2"
|
||||
},
|
||||
{
|
||||
"from": 4839,
|
||||
"to": 4848,
|
||||
"pattern": "D-1"
|
||||
},
|
||||
{
|
||||
"from": 4848,
|
||||
"to": 4857,
|
||||
"pattern": "C-2"
|
||||
},
|
||||
{
|
||||
"from": 4857,
|
||||
"to": 4862,
|
||||
"pattern": "C-1"
|
||||
},
|
||||
{
|
||||
"from": 4862,
|
||||
"to": 4867,
|
||||
"pattern": "B-2"
|
||||
},
|
||||
{
|
||||
"from": 4867,
|
||||
"to": 4873,
|
||||
"pattern": "B-1"
|
||||
},
|
||||
{
|
||||
"from": 4873,
|
||||
"to": 4907,
|
||||
"pattern": "A"
|
||||
},
|
||||
{
|
||||
"from": 4907,
|
||||
"to": 4915,
|
||||
"pattern": "B-1"
|
||||
},
|
||||
{
|
||||
"from": 4915,
|
||||
"to": 4920,
|
||||
"pattern": "B-2"
|
||||
},
|
||||
{
|
||||
"from": 4920,
|
||||
"to": 4940,
|
||||
"pattern": "RP-1"
|
||||
},
|
||||
{
|
||||
"from": 4940,
|
||||
"to": 4988,
|
||||
"pattern": "C-1"
|
||||
},
|
||||
{
|
||||
"from": 4988,
|
||||
"to": 4998,
|
||||
"pattern": "B-2"
|
||||
},
|
||||
{
|
||||
"from": 4998,
|
||||
"to": 5006,
|
||||
"pattern": "B-1"
|
||||
},
|
||||
{
|
||||
"from": 5006,
|
||||
"to": 5019,
|
||||
"pattern": "B-2"
|
||||
},
|
||||
{
|
||||
"from": 5019,
|
||||
"to": 5025,
|
||||
"pattern": "B-1"
|
||||
},
|
||||
{
|
||||
"from": 5025,
|
||||
"to": 5110,
|
||||
"pattern": "A"
|
||||
},
|
||||
{
|
||||
"from": 5110,
|
||||
"to": 5130,
|
||||
"pattern": "RP-1"
|
||||
},
|
||||
{
|
||||
"from": 5130,
|
||||
"to": 5140,
|
||||
"pattern": "B-1"
|
||||
},
|
||||
{
|
||||
"from": 5140,
|
||||
"to": 5157,
|
||||
"pattern": "B-2"
|
||||
},
|
||||
{
|
||||
"from": 5157,
|
||||
"to": 5204,
|
||||
"pattern": "C-1"
|
||||
},
|
||||
{
|
||||
"from": 5204,
|
||||
"to": 5211,
|
||||
"pattern": "B-2"
|
||||
},
|
||||
{
|
||||
"from": 5211,
|
||||
"to": 5221,
|
||||
"pattern": "B-1"
|
||||
},
|
||||
{
|
||||
"from": 5221,
|
||||
"to": 5259,
|
||||
"pattern": "A"
|
||||
},
|
||||
{
|
||||
"from": 5259,
|
||||
"to": 5279,
|
||||
"pattern": "B-1"
|
||||
},
|
||||
{
|
||||
"from": 5279,
|
||||
"to": 5300,
|
||||
"pattern": "A"
|
||||
},
|
||||
{
|
||||
"from": 5300,
|
||||
"to": 5320,
|
||||
"pattern": "RP-1"
|
||||
},
|
||||
{
|
||||
"from": 5320,
|
||||
"to": 5357,
|
||||
"pattern": "A"
|
||||
},
|
||||
{
|
||||
"from": 5357,
|
||||
"to": 5363,
|
||||
"pattern": "B-1"
|
||||
},
|
||||
{
|
||||
"from": 5363,
|
||||
"to": 5377,
|
||||
"pattern": "B-2"
|
||||
},
|
||||
{
|
||||
"from": 5377,
|
||||
"to": 5390,
|
||||
"pattern": "C-1"
|
||||
},
|
||||
{
|
||||
"from": 5390,
|
||||
"to": 5399,
|
||||
"pattern": "C-2"
|
||||
},
|
||||
{
|
||||
"from": 5399,
|
||||
"to": 5406,
|
||||
"pattern": "D-1"
|
||||
},
|
||||
{
|
||||
"from": 5406,
|
||||
"to": 5427,
|
||||
"pattern": "D-2"
|
||||
},
|
||||
{
|
||||
"from": 5427,
|
||||
"to": 5446,
|
||||
"pattern": "D-1"
|
||||
},
|
||||
{
|
||||
"from": 5446,
|
||||
"to": 5462,
|
||||
"pattern": "WL-2"
|
||||
},
|
||||
{
|
||||
"from": 5462,
|
||||
"to": 5468,
|
||||
"pattern": "P-2"
|
||||
},
|
||||
{
|
||||
"from": 5468,
|
||||
"to": 5495,
|
||||
"pattern": "P-3"
|
||||
},
|
||||
{
|
||||
"from": 5495,
|
||||
"to": 5500,
|
||||
"pattern": "개착부"
|
||||
}
|
||||
],
|
||||
"open": [
|
||||
"4+700~4+745 and 5+480~5+500 excluded ([미확정])",
|
||||
"지보패턴 측점표 적용(당진). 도면 E-1~2, RP-2, WL-3~4 는 당진 표에 없어 [미확정]. 대산 표 WL-4(4+758.85~4+768.85)는 참고만 [확인 필요]",
|
||||
"lining reference recorded (meta.lining_reference, 04-004~009); sheet-to-station mapping [미확정]",
|
||||
"[확정 2026-10-21] R1 6,982 mm 유지 (사용자 결정). 라이닝 도면 6장의 R1 6.892 m 표기는 확인 대상 오기로 기록",
|
||||
"pattern-group to excavation-pattern mapping: user explanation conflicts with filename (WL-4) - re-confirm [확인 필요]"
|
||||
],
|
||||
"lining_reference": {
|
||||
"source": "C0060304-004..009 (콘크리트라이닝구조도)",
|
||||
"concrete": "fck 27 MPa",
|
||||
"rebar": "SD400 (fy 400 MPa)",
|
||||
"aggregate_mm": 25,
|
||||
"summary": "C0060304-004~009 | fck 27 MPa | SD400 | 입출구부 50 m 취약구간 철근 상세 (측점 대응 미확정)",
|
||||
"sheets": [
|
||||
{
|
||||
"sheet": "C0060304-004-콘",
|
||||
"groups": [
|
||||
[
|
||||
"A~C-2",
|
||||
"입출구부 50m"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"sheet": "C0060304-005-콘",
|
||||
"groups": [
|
||||
[
|
||||
"E-1~2",
|
||||
"온도미고려"
|
||||
],
|
||||
[
|
||||
"D-1~2",
|
||||
"입출구부 50m"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"sheet": "C0060304-006-콘",
|
||||
"groups": [
|
||||
[
|
||||
"WL-1~2,RP-1~2",
|
||||
"온도미고려"
|
||||
],
|
||||
[
|
||||
"E-1~2",
|
||||
"입출구부 50m"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"sheet": "C0060304-007-콘",
|
||||
"groups": [
|
||||
[
|
||||
"WL-1~2",
|
||||
"입출구부 50m"
|
||||
],
|
||||
[
|
||||
"WL-3~4",
|
||||
"온도미고려"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"sheet": "C0060304-008-콘",
|
||||
"groups": [
|
||||
[
|
||||
"WL-3~4",
|
||||
"입출구부 50m"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"sheet": "C0060304-009-콘",
|
||||
"groups": [
|
||||
[
|
||||
"P-2~3",
|
||||
"입출구부 50m"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Task 5 (user decision 2026-10-21): no pattern-station data exists. Create a
|
||||
PLACEHOLDER pattern table at 2 m segments over the modelled range 4+745..5+480,
|
||||
cycling the six pattern names. Every row is marked '임의 배정 (placeholder)'.
|
||||
The table is an editable CSV: replace the 'pattern' column later and rebuild.
|
||||
Output: data/pattern_zones_placeholder.csv (created only if absent; never overwritten)."""
|
||||
import csv
|
||||
import os
|
||||
import sys
|
||||
|
||||
ROOT = sys.argv[1]
|
||||
OUT = f"{ROOT}/data/pattern_zones_placeholder.csv"
|
||||
PATTERNS = ["NP-4AD-1-D-2", "NP-4BD-1-D-2", "NP-5AE-1-WL-4", "NP-5BE-1-WL-4", "NP-6A", "NP-6B"]
|
||||
S0, S1, SEG = 4745, 5480, 2
|
||||
if not os.path.exists(OUT):
|
||||
with open(OUT, "w", newline="", encoding="utf-8") as fh:
|
||||
w = csv.writer(fh)
|
||||
w.writerow(["seg_from", "seg_to", "pattern", "status"])
|
||||
for k, s in enumerate(range(S0, S1, SEG)):
|
||||
w.writerow([s, min(s + SEG, S1), PATTERNS[k % len(PATTERNS)], "임의 배정 (placeholder, 실제 구간 아님)"])
|
||||
print("pattern table:", OUT)
|
||||
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Task 7: axis + standard-section sweep skeleton of the main line (본선), 4+745..5+480.
|
||||
|
||||
Inputs (all confirmed by the user, 2026-10-21):
|
||||
- centreline nodes data/alignment_plan_001_centre.csv (midpoint of side ticks)
|
||||
- profile data/profile_gl_002_draft.csv (A = 지반고 GL, B = 계획고 = 노면)
|
||||
- section table DOC/03_설계/설계_표준단면치수표_2026-10-21_v0.1.00.md
|
||||
R1 6,982 mm (lining inner), lining 300 mm -> outer R 7,282, excavation R 7,402
|
||||
crown A = 6.615 m above road (F = 0.000 = road reference)
|
||||
- cross slope: 4+745~5+105 -2 %, 5+105~5+165 linear -2 -> +2 %, 5+165~5+480 +2 %
|
||||
Model (skeleton, not as-built):
|
||||
- Section = arch (concentric centre c = 6.615 - 6.982) + vertical walls down to the road.
|
||||
Walls and invert are APPROXIMATIONS and are flagged in the metadata. No invert.
|
||||
- Superelevation: z += (S/100) * x across the section (sign convention ASSUMED, flagged).
|
||||
- Coordinates: plan XY in m (station spacing 100 m matches plan units), origin at 4+745.
|
||||
Outputs: model/tunnel_skeleton.glb, model/tunnel_skeleton_meta.json, data/verify_skeleton.json
|
||||
"""
|
||||
import csv
|
||||
import json
|
||||
import math
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
import trimesh
|
||||
|
||||
ROOT = sys.argv[1] # Code/3d-drawing
|
||||
C_CSV = f"{ROOT}/data/alignment_plan_001_centre.csv"
|
||||
P_CSV = f"{ROOT}/data/profile_gl_002_draft.csv"
|
||||
R1, R_OUT, R_EX = 6.982, 7.282, 7.402 # m (mm / 1000)
|
||||
CROWN_A = 6.615 # m above road
|
||||
CENTRE_C = CROWN_A - R1 # arch centre height above road (m)
|
||||
S0, S1 = 4745, 5480 # modelled range (fully determined)
|
||||
DS = 1.0 # station step for rings (m); pattern segments are 2 m
|
||||
ANG = np.radians(np.linspace(40.0, 140.0, 41)) # arch arc, right springline -> left
|
||||
|
||||
def read_centre():
|
||||
rows = [r for r in csv.DictReader(open(C_CSV, encoding="utf-8"))]
|
||||
return np.array([[int(r["station_m"]), float(r["centre_x"]), float(r["centre_y"])] for r in rows])
|
||||
|
||||
def read_profile():
|
||||
gl, dg = {}, {}
|
||||
for r in csv.DictReader(open(P_CSV, encoding="utf-8")):
|
||||
sta = int(r["station_anchor"])
|
||||
(gl if float(r["across_m"]) < -25 else dg)[sta] = float(r["value"])
|
||||
return gl, dg
|
||||
|
||||
def slope_pct(s):
|
||||
if s < 5105: return -2.0
|
||||
if s < 5165: return -2.0 + 4.0 * (s - 5105) / 60.0
|
||||
return 2.0
|
||||
|
||||
cn = read_centre()
|
||||
gl_d, dg_d = read_profile()
|
||||
gl_s = np.array(sorted(gl_d)); gl_v = np.array([gl_d[k] for k in gl_s])
|
||||
dg_s = np.array(sorted(dg_d)); dg_v = np.array([dg_d[k] for k in dg_s])
|
||||
cn_s = cn[:, 0]
|
||||
|
||||
def axis_xy(s):
|
||||
x = np.interp(s, cn_s, cn[:, 1]); y = np.interp(s, cn_s, cn[:, 2])
|
||||
i = min(max(np.searchsorted(cn_s, s) - 1, 0), len(cn_s) - 2)
|
||||
dx, dy = cn[i + 1, 1] - cn[i, 1], cn[i + 1, 2] - cn[i, 2]
|
||||
L = math.hypot(dx, dy)
|
||||
return x, y, dx / L, dy / L
|
||||
|
||||
ORIGIN = np.array(axis_xy(S0)[:2])
|
||||
stations = np.arange(S0, S1 + 0.001, DS)
|
||||
|
||||
def section(R, s, slope):
|
||||
"""ring (x lateral m, z above road m) for radius R, including superelevation."""
|
||||
xs = np.concatenate([[R * math.cos(ANG[0])], R * np.cos(ANG), [R * math.cos(ANG[-1])]])
|
||||
zs = np.concatenate([[0.0], CENTRE_C + R * np.sin(ANG), [0.0]])
|
||||
zs = zs + slope * xs
|
||||
return xs, zs
|
||||
|
||||
def ring_world(R, s):
|
||||
x, y, tx, ty = axis_xy(s)
|
||||
nx, ny = -ty, tx # left normal (horizontal)
|
||||
zr = np.interp(s, dg_s, dg_v) # road (계획고 = 노면)
|
||||
xs, zs = section(R, s, slope_pct(s) / 100.0)
|
||||
P = np.column_stack([x - ORIGIN[0] + xs * nx, y - ORIGIN[1] + xs * ny, zr + zs])
|
||||
return P
|
||||
|
||||
PAL = {"A": (42, 157, 143, 255), "B-1": (231, 111, 81, 255), "B-2": (244, 162, 97, 255),
|
||||
"C-1": (233, 196, 106, 255), "C-2": (138, 177, 125, 255), "D-1": (38, 70, 83, 255),
|
||||
"D-2": (100, 130, 160, 255), "P-2": (190, 110, 140, 255), "P-3": (150, 90, 60, 255),
|
||||
"RP-1": (80, 170, 200, 255), "WL-1": (200, 200, 120, 255), "WL-2": (120, 90, 170, 255),
|
||||
"개착부": (170, 170, 170, 255)}
|
||||
ZONES = [(int(float(r["sta_from"])), int(float(r["sta_to"])), r["lining_pattern"], "엑셀 지보패턴표(당진방향)")
|
||||
for r in csv.DictReader(open(f"{ROOT}/data/pattern_stations_dangjin.csv", encoding="utf-8"))]
|
||||
|
||||
def pattern_of(sta):
|
||||
for a, b, pat, _ in ZONES:
|
||||
if a <= sta < b:
|
||||
return pat
|
||||
return ZONES[-1][2]
|
||||
|
||||
def make_band_pattern(R, name):
|
||||
rings = [ring_world(R, s) for s in stations]
|
||||
n = len(rings[0]); m = len(rings)
|
||||
V = np.vstack(rings)
|
||||
F, C = [], []
|
||||
for i in range(m - 1):
|
||||
col = PAL[pattern_of(stations[i])]
|
||||
for j in range(n - 1):
|
||||
a = i * n + j; b = a + 1; c = a + n; d = c + 1
|
||||
F += [[a, b, c], [b, d, c]]
|
||||
C += [col, col]
|
||||
mesh = trimesh.Trimesh(vertices=V, faces=np.array(F), process=False)
|
||||
mesh.visual.face_colors = np.array(C, dtype=np.uint8)
|
||||
mesh.metadata["name"] = name
|
||||
mesh.metadata["ring_points"] = n
|
||||
return mesh
|
||||
|
||||
def make_ribbon(z_fn, half_w, color, name):
|
||||
V, F = [], []
|
||||
for k, s in enumerate(stations):
|
||||
x, y, tx, ty = axis_xy(s)
|
||||
nx, ny = -ty, tx
|
||||
zr = z_fn(s)
|
||||
V.append([x - ORIGIN[0] - half_w * nx, y - ORIGIN[1] - half_w * ny, zr])
|
||||
V.append([x - ORIGIN[0] + half_w * nx, y - ORIGIN[1] + half_w * ny, zr])
|
||||
V = np.array(V)
|
||||
for k in range(len(stations) - 1):
|
||||
a = 2 * k; b = a + 1; c = a + 2; d = a + 3
|
||||
F += [[a, c, b], [b, c, d]]
|
||||
mesh = trimesh.Trimesh(vertices=V, faces=np.array(F), process=False)
|
||||
mesh.visual.face_colors = np.tile(np.array(color, dtype=np.uint8), (len(F), 1))
|
||||
mesh.metadata["name"] = name
|
||||
return mesh
|
||||
|
||||
def make_band(R, color, name):
|
||||
"""uniform-colour band (excavation outline)."""
|
||||
rings = [ring_world(R, s) for s in stations]
|
||||
n = len(rings[0]); m = len(rings)
|
||||
V = np.vstack(rings)
|
||||
F = []
|
||||
for i in range(m - 1):
|
||||
for j in range(n - 1):
|
||||
a = i * n + j; b = a + 1; c = a + n; d = c + 1
|
||||
F += [[a, b, c], [b, d, c]]
|
||||
mesh = trimesh.Trimesh(vertices=V, faces=np.array(F), process=False)
|
||||
mesh.visual.face_colors = np.tile(np.array(color, dtype=np.uint8), (len(F), 1))
|
||||
mesh.metadata["name"] = name
|
||||
return mesh
|
||||
|
||||
parts = [
|
||||
make_band_pattern(R_OUT, "lining_outer_R7282"),
|
||||
make_band_pattern(R1, "lining_inner_R6982"),
|
||||
make_band(R_EX, (150, 120, 90, 110), "excavation_R7402"),
|
||||
make_ribbon(lambda s: float(np.interp(s, dg_s, dg_v)), 5.0, (70, 70, 75, 255), "road_surface_계획고"),
|
||||
make_ribbon(lambda s: float(np.interp(s, gl_s, gl_v)), 20.0, (110, 150, 90, 200), "ground_GL_지반고"),
|
||||
]
|
||||
scene = trimesh.Scene()
|
||||
for p in parts:
|
||||
scene.add_geometry(p, geom_name=p.metadata["name"])
|
||||
glb = f"{ROOT}/model/tunnel_skeleton.glb"
|
||||
scene.export(glb)
|
||||
|
||||
# ---- verification (assertions recorded, not forced) ----
|
||||
checks = {}
|
||||
# radius check in section plane: distance from arch centre (lateral=0, z=centre_c)
|
||||
xs, zs = section(R1, S0, 0.0)
|
||||
rad = np.hypot(xs[1:-1], zs[1:-1] - CENTRE_C)
|
||||
checks["inner_radius_max_dev_m"] = float(np.max(np.abs(rad - R1)))
|
||||
checks["lining_thickness_crown_m"] = round(R_OUT - R1, 3)
|
||||
crown_z = ring_world(R1, 5000)[:, 2].max() - np.interp(5000, dg_s, dg_v)
|
||||
checks["crown_above_road_m (expect 6.615 at S=0 station 5000)"] = round(float(crown_z), 4)
|
||||
checks["road_z_4745_m"] = round(float(np.interp(S0, dg_s, dg_v)), 3)
|
||||
checks["road_z_5480_m"] = round(float(np.interp(S1, dg_s, dg_v)), 3)
|
||||
L_chord = float(np.sum([math.dist(cn[i, 1:], cn[i + 1, 1:]) for i in range(len(cn) - 1)]))
|
||||
checks["plan_chord_length_total_m (4+700..5+500)"] = round(L_chord, 3)
|
||||
checks["station_range_modelled"] = f"{S0} .. {S1} (ring step {DS} m, {len(stations)} rings)"
|
||||
checks["pattern_zones (지보패턴표 당진방향)"] = len(ZONES)
|
||||
checks["pattern_coverage_all_stations"] = bool(all(pattern_of(x) in PAL for x in stations))
|
||||
checks["pattern_counts"] = {k: sum(1 for z in ZONES if z[2] == k) for k in PAL}
|
||||
checks["glb_bytes"] = int(__import__("os").path.getsize(glb))
|
||||
reload = trimesh.load(glb)
|
||||
checks["glb_reload_geometries"] = len(reload.geometry)
|
||||
json.dump(checks, open(f"{ROOT}/data/verify_skeleton.json", "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
||||
meta = {
|
||||
"model": "tunnel_skeleton.glb", "scope": f"{S0}~{S1} (4+745~5+480)",
|
||||
"units": "m", "origin_plan_xy": [round(ORIGIN[0], 3), round(ORIGIN[1], 3)],
|
||||
"sources": {"centreline": "C0060302-001 CA-ALGN-STSM (side-tick midpoint)",
|
||||
"road_grade_B": "C0060302-002 CR-TABL-TEX2 (계획고=노면)",
|
||||
"GL_A": "C0060302-002 CR-TABL-TEX2 (지반고)",
|
||||
"section": "C0060303-001 CS-TABL-TEX1/2, CS-CONC-MAJR, CS-EXCV"},
|
||||
"approximations": ["walls vertical down to road; invert not modelled",
|
||||
"lining band thickness at walls is radial-consistent but not as-built",
|
||||
"centreline and grade linearly interpolated between nodes",
|
||||
"superelevation sign convention assumed (z += S/100 * x, x = left of tangent)"],
|
||||
"layers": [p.metadata["name"] for p in parts],
|
||||
"ring_step_m": DS, "ring_points": int(parts[0].metadata["ring_points"]), "first_station": S0,
|
||||
"pattern_status": "지보패턴: 엑셀 참고_지보패턴현황 당진방향 표(4+740~5+500, 42행, 빈틈 없음) 적용. 출처 data/pattern_stations_dangjin.csv (사용자 결정 2026-10-21)",
|
||||
"patterns": [{"from": a, "to": b, "pattern": pat} for a, b, pat, _ in ZONES],
|
||||
"open": ["4+700~4+745 and 5+480~5+500 excluded ([미확정])", "지보패턴 측점표 적용(당진). 도면 E-1~2, RP-2, WL-3~4 는 당진 표에 없어 [미확정]. 대산 표 WL-4(4+758.85~4+768.85)는 참고만 [확인 필요]",
|
||||
"lining reference recorded (meta.lining_reference, 04-004~009); sheet-to-station mapping [미확정]",
|
||||
"[확정 2026-10-21] R1 6,982 mm 유지 (사용자 결정). 라이닝 도면 6장의 R1 6.892 m 표기는 확인 대상 오기로 기록", "pattern-group to excavation-pattern mapping: user explanation conflicts with filename (WL-4) - re-confirm [확인 필요]"]
|
||||
}
|
||||
_ls = json.load(open(f"{ROOT}/data/lining_spec.json", encoding="utf-8"))
|
||||
meta["lining_reference"] = {
|
||||
"source": _ls["source"], "concrete": "fck 27 MPa", "rebar": "SD400 (fy 400 MPa)", "aggregate_mm": 25,
|
||||
"summary": "C0060304-004~009 | fck 27 MPa | SD400 | 입출구부 50 m 취약구간 철근 상세 (측점 대응 미확정)",
|
||||
"sheets": [{"sheet": r["file"][:14], "groups": [[g["group"], g["kind"]] for g in r["pattern_details"]]} for r in _ls["sheets"]]
|
||||
}
|
||||
json.dump(meta, open(f"{ROOT}/model/tunnel_skeleton_meta.json", "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
||||
print(json.dumps(checks, ensure_ascii=False, indent=1))
|
||||
@@ -0,0 +1,329 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Task 8: B-rep solids of the main line (본선) 4+745..5+480 -> STEP (Rhino/Autodesk) + GLB (web viewer).
|
||||
|
||||
Section geometry: standard section C0060303-001 (drawing 03-001), revision rev2 approved 2026-10-21.
|
||||
- lining (라이닝): arch R6982 (inner) / R7282 (outer) about centre C; haunch arcs R3712/R3910 (inner),
|
||||
R4454/R4699 (outer) run below road level as drawn; inner walls and inner chord (road level) as drawn.
|
||||
Lining is symmetric (no superelevation). H/I boxes removed from lining (2D).
|
||||
- excavation band (굴착 여유대): R7402 / R4574 / R4819 minus lining outer, above road level. Symmetric.
|
||||
- road surface / 계획고 (노면, 포장 280 mm): cross slope S per station (superelevation). Only this part tilts.
|
||||
- base / invert (기층·인버트, ASSUMED: not in drawing): region between lining outer and inner chord minus
|
||||
pavement and side drains. Its top follows the tilted pavement bottom.
|
||||
- side drains (측구): box 300 x 836 mm minus pipe r150 (drawing circles).
|
||||
- H/I boxes (공동구, ASSUMED by user decision 2026-10-21).
|
||||
- 시설한계 50 mm: REFERENCE polylines only (not solids), as decided by the user.
|
||||
Coordinates: local section x = lateral along the LEFT normal of the alignment (sign convention flagged),
|
||||
z = height above road centre. Superelevation: z += (S/100)*x (sign flagged).
|
||||
Outputs: model/tunnel_solids.step, model/tunnel_solids.glb, model/tunnel_solids_meta.json, data/verify_solids.json
|
||||
Usage: python3 scripts/build_solids.py "$PWD"
|
||||
"""
|
||||
import csv, json, math, os, sys, tempfile
|
||||
import numpy as np
|
||||
from shapely.geometry import Polygon, box
|
||||
from build123d import Vector, Wire, Solid, Compound, Color, export_step, export_stl, import_step
|
||||
import trimesh
|
||||
|
||||
ROOT = sys.argv[1]
|
||||
MODE = sys.argv[2] if len(sys.argv) > 2 else 'all' # parts_a | parts_b | assemble (4 GB RAM: run in stages)
|
||||
PARTS = f"{ROOT}/model/parts"
|
||||
RANGE = (int(sys.argv[3]), int(sys.argv[4])) if len(sys.argv) > 4 else (0, 10**6) # run index range for parts_a
|
||||
os.makedirs(PARTS, exist_ok=True)
|
||||
C_CSV = f"{ROOT}/data/alignment_plan_001_centre.csv"
|
||||
P_CSV = f"{ROOT}/data/profile_gl_002_draft.csv"
|
||||
PAT_CSV = f"{ROOT}/data/pattern_stations_dangjin.csv"
|
||||
S0, S1 = 4745, 5480
|
||||
STEP_M = 10.0 # section spacing (m): ruled loft between sections (STEP size vs curvature)
|
||||
CHUNK_M = 100
|
||||
PIPE_SEG = 24
|
||||
CLEAR_OFFSET = 0.05
|
||||
CLEAR_LINES = 6
|
||||
CLEAR_RIB_W = 0.02
|
||||
|
||||
# ---------- drawing geometry (mm) -> local m ----------
|
||||
CX, CY, Y0 = 14126.29, 15305.02, 15672.0
|
||||
def P(x, y): return ((x - CX) / 1000.0, (y - Y0) / 1000.0)
|
||||
def arc(cx, cy, r, a0, a1, n=120):
|
||||
return [(cx + r * math.cos(math.radians(a0 + (a1 - a0) * i / n)),
|
||||
cy + r * math.sin(math.radians(a0 + (a1 - a0) * i / n))) for i in range(n + 1)]
|
||||
def loc(pts): return [P(x, y) for x, y in pts]
|
||||
WALL_L_INNER = [(8771, 15751), (8771, 15826), (8651, 16001), (8621, 16411), (8471, 16411),
|
||||
(8171, 16411), (8045, 16411), (8034, 16446)]
|
||||
WALL_R_INNER = [(19481, 15567), (19481, 15642), (19601, 15817), (19631, 16227), (19781, 16227),
|
||||
(20181, 16227), (20251, 16239)]
|
||||
INNER_MM = (WALL_L_INNER + arc(11621.3, 17406.9, 3712, 195.6, 140) + arc(CX, CY, 6982, 140, 40)
|
||||
+ arc(16479.6, 17279.7, 3910, 40, -15.6) + WALL_R_INNER[::-1])
|
||||
OUTER_MM = (arc(11959.9, 17122.8, 4454, 204.7, 140) + arc(CX, CY, 7282, 140, 40)
|
||||
+ arc(16105.0, 16965.3, 4699, 40, -23.7))
|
||||
EX_MM = (arc(11959.9, 17122.8, 4574, 204.0, 140) + arc(CX, CY, 7402, 140, 40)
|
||||
+ arc(16105.0, 16965.3, 4819, 40, -23.0))
|
||||
inner_poly = Polygon(loc(INNER_MM)).buffer(0)
|
||||
outer_poly = Polygon(loc(OUTER_MM)).buffer(0)
|
||||
ex_poly = Polygon(loc(EX_MM)).buffer(0)
|
||||
def _fill_slivers(g, tol=1e-3):
|
||||
if g.geom_type != 'Polygon': return g
|
||||
return Polygon(g.exterior, [h for h in g.interiors if Polygon(h).area > tol])
|
||||
ring = _fill_slivers(outer_poly.difference(inner_poly)) # 37 mm wall/haunch join leaves 1e-5 m2 slivers
|
||||
|
||||
CHORD_A, CHORD_B = P(8771, 15751), P(19481, 15567)
|
||||
_k = (CHORD_B[1] - CHORD_A[1]) / (CHORD_B[0] - CHORD_A[0])
|
||||
def chord_z(x): return CHORD_A[1] + _k * (x - CHORD_A[0])
|
||||
below_chord = Polygon([(-9, chord_z(-9)), (9, chord_z(9)), (9, -5), (-9, -5)])
|
||||
above_chord = Polygon([(-9, chord_z(-9)), (9, chord_z(9)), (9, 9), (-9, 9)])
|
||||
|
||||
XL = P(9526, 15764)[0]; XR = P(18726, 15580)[0]
|
||||
T_FIX = -0.02
|
||||
PAV_T = 0.28
|
||||
def pav_corners(t):
|
||||
return [(XL, t * XL), (XR, t * XR), (XR, t * XR - PAV_T), (XL, t * XL - PAV_T)]
|
||||
pav_fix = Polygon(pav_corners(T_FIX))
|
||||
|
||||
def rect_local(x0_mm, x1_mm, y0_mm, y1_mm):
|
||||
(a, b), (c, d) = P(x0_mm, y0_mm), P(x1_mm, y1_mm)
|
||||
return box(min(a, c), min(b, d), max(a, c), max(b, d))
|
||||
DRAIN_L = rect_local(9101, 9401, 14864, 15700)
|
||||
DRAIN_R = rect_local(18851, 19151, 14680, 15520)
|
||||
PIPE_L = (P(9251.3, 15064)[0], P(9251.3, 15064)[1], 0.150)
|
||||
PIPE_R = (P(19001.3, 14880)[0], P(19001.3, 14880)[1], 0.150)
|
||||
HBOX = rect_local(8171, 8471, 16051, 16411)
|
||||
IBOX = rect_local(19781, 20181, 15867, 16227)
|
||||
|
||||
lin_full = _fill_slivers(ring.intersection(above_chord))
|
||||
assert lin_full.geom_type == 'Polygon' and len(lin_full.interiors) == 0, f"lining topology: {lin_full.geom_type}"
|
||||
LINING_POLY = lin_full.simplify(0.01, preserve_topology=True) # 10 mm: removes 30 um edges (loft invalid) and cuts ~700 -> ~60 section vertices (STEP size)
|
||||
EXCAV_POLY = ex_poly.difference(outer_poly).intersection(box(-9, 0.0, 9, 9)).simplify(0.01, preserve_topology=True)
|
||||
assert EXCAV_POLY.geom_type == 'Polygon' and len(EXCAV_POLY.interiors) == 0, f"excavation topology: {EXCAV_POLY.geom_type}"
|
||||
base_fix = ring.intersection(below_chord).difference(pav_fix).difference(DRAIN_L).difference(DRAIN_R)
|
||||
assert base_fix.geom_type == 'Polygon' and len(base_fix.interiors) == 0, f"base topology: {base_fix.geom_type}"
|
||||
BASE_PTS_FIX = np.array(base_fix.exterior.coords)[:-1]
|
||||
def _find(pt):
|
||||
d = np.hypot(BASE_PTS_FIX[:, 0] - pt[0], BASE_PTS_FIX[:, 1] - pt[1])
|
||||
i = int(np.argmin(d)); assert d[i] < 1e-6, f"pavement corner not on base boundary: {d[i]}"
|
||||
return i
|
||||
IDX_BL = _find((XL, T_FIX * XL - PAV_T)); IDX_BR = _find((XR, T_FIX * XR - PAV_T))
|
||||
def base_pts(t):
|
||||
pts = BASE_PTS_FIX.copy()
|
||||
pts[IDX_BL, 1] = t * XL - PAV_T
|
||||
pts[IDX_BR, 1] = t * XR - PAV_T
|
||||
return pts
|
||||
LIN_PTS = np.array(LINING_POLY.exterior.coords)[:-1]
|
||||
EXC_PTS = np.array(EXCAV_POLY.exterior.coords)[:-1]
|
||||
def PIPE_PTS(cx, cz, r):
|
||||
return np.array([(cx + r * math.cos(2 * math.pi * k / PIPE_SEG), cz + r * math.sin(2 * math.pi * k / PIPE_SEG)) for k in range(PIPE_SEG)])
|
||||
DRAIN_L_PTS = np.array(DRAIN_L.exterior.coords)[:-1]
|
||||
DRAIN_R_PTS = np.array(DRAIN_R.exterior.coords)[:-1]
|
||||
HBOX_PTS = np.array(HBOX.exterior.coords)[:-1]
|
||||
IBOX_PTS = np.array(IBOX.exterior.coords)[:-1]
|
||||
|
||||
# ---------- alignment, profile ----------
|
||||
cn = np.array([[int(r["station_m"]), float(r["centre_x"]), float(r["centre_y"])]
|
||||
for r in csv.DictReader(open(C_CSV, encoding="utf-8"))])
|
||||
dg_d = {}
|
||||
for r in csv.DictReader(open(P_CSV, encoding="utf-8")):
|
||||
if float(r["across_m"]) >= -25:
|
||||
dg_d[int(r["station_anchor"])] = float(r["value"])
|
||||
dg_s = np.array(sorted(dg_d)); dg_v = np.array([dg_d[k] for k in dg_s])
|
||||
cn_s = cn[:, 0]
|
||||
def axis(s):
|
||||
x = np.interp(s, cn_s, cn[:, 1]); y = np.interp(s, cn_s, cn[:, 2])
|
||||
i = min(max(np.searchsorted(cn_s, s) - 1, 0), len(cn_s) - 2)
|
||||
dx, dy = cn[i + 1, 1] - cn[i, 1], cn[i + 1, 2] - cn[i, 2]
|
||||
L = math.hypot(dx, dy)
|
||||
return x, y, dx / L, dy / L
|
||||
ORX, ORY = axis(S0)[0], axis(S0)[1]
|
||||
def slope_pct(s):
|
||||
if s < 5105: return -2.0
|
||||
if s < 5165: return -2.0 + 4.0 * (s - 5105) / 60.0
|
||||
return 2.0
|
||||
def world(s, x, z):
|
||||
ax, ay, tx, ty = axis(s)
|
||||
nx, ny = -ty, tx
|
||||
zr = float(np.interp(s, dg_s, dg_v))
|
||||
return (ax - ORX + x * nx, ay - ORY + x * ny, zr + z)
|
||||
|
||||
# ---------- B-rep helpers ----------
|
||||
def loft(stations, pts2_fn):
|
||||
wires = []
|
||||
for s in stations:
|
||||
pts3 = [world(s, float(x), float(z)) for x, z in pts2_fn(s)]
|
||||
wires.append(Wire.make_polygon([Vector(*p) for p in pts3], close=True))
|
||||
sol = Solid.make_loft(wires, ruled=True)
|
||||
if not sol.is_valid: # end caps of lofts with near-degenerate edges; ShapeFix repairs them
|
||||
sol = sol.fix()
|
||||
return sol
|
||||
def stations_of(a, b, step=STEP_M):
|
||||
n = max(1, int(round((b - a) / step)))
|
||||
return [float(v) for v in np.linspace(a, b, n + 1)]
|
||||
def chunks():
|
||||
out = []; a = S0
|
||||
while a < S1:
|
||||
b = min(a + CHUNK_M, S1); out.append((a, b)); a = b
|
||||
return out
|
||||
|
||||
PAL = {"A": (42, 157, 143), "B-1": (231, 111, 81), "B-2": (244, 162, 97), "C-1": (233, 196, 106),
|
||||
"C-2": (138, 177, 125), "D-1": (38, 70, 83), "D-2": (100, 130, 160), "P-2": (190, 110, 140),
|
||||
"P-3": (150, 90, 60), "RP-1": (80, 170, 200), "WL-1": (200, 200, 120), "WL-2": (120, 90, 170),
|
||||
"개착부": (170, 170, 170)}
|
||||
COL = {"base": (141, 110, 99), "pav": (96, 125, 139), "drain": (30, 136, 229), "hi": (253, 216, 53),
|
||||
"clear": (255, 111, 0)}
|
||||
|
||||
runs = []
|
||||
for r in csv.DictReader(open(PAT_CSV, encoding="utf-8")):
|
||||
a, b = int(float(r["sta_from"])), int(float(r["sta_to"])); pat = r["lining_pattern"]
|
||||
a2, b2 = max(a, S0), min(b, S1)
|
||||
if b2 > a2: runs.append((a2, b2, pat))
|
||||
runs.sort()
|
||||
assert runs[0][0] == S0 and runs[-1][1] == S1, f"pattern coverage {runs[0][0]}..{runs[-1][1]}"
|
||||
for (a1, b1, _), (a2, _, _) in zip(runs, runs[1:]):
|
||||
assert b1 == a2, f"pattern gap/overlap at {b1} vs {a2}"
|
||||
|
||||
solids, meta_c, clear_wires = [], [], []
|
||||
def add(sol, cid, label, group, color, pattern=None, a=None, b=None):
|
||||
if not sol.is_valid:
|
||||
sol = sol.fix() # booleans on lofts can leave cap faces invalid
|
||||
assert sol.is_valid, f"invalid solid {cid}"
|
||||
sol.label = cid; sol.color = Color(*[c / 255 for c in color])
|
||||
solids.append(sol)
|
||||
meta_c.append({"id": cid, "label": label, "group": group, "pattern": pattern,
|
||||
"from_sta": a, "to_sta": b, "volume_m3": round(sol.volume, 4), "color": list(color)})
|
||||
|
||||
for i, (a, b, pat) in enumerate(runs):
|
||||
if MODE != 'parts_a' or not (RANGE[0] <= i < RANGE[1]): continue
|
||||
print('lining run', i + 1, pat, a, b, flush=True)
|
||||
lin_run = loft(stations_of(a, b), lambda s: LIN_PTS)
|
||||
lin_run = lin_run - loft(stations_of(a, b), lambda s: HBOX_PTS) - loft(stations_of(a, b), lambda s: IBOX_PTS)
|
||||
add(lin_run, f"LIN_{i+1:02d}_{pat}", f"라이닝 {pat} {a/1000:.3f}~{b/1000:.3f}",
|
||||
"라이닝", PAL[pat], pat, a, b)
|
||||
for i, (a, b, pat) in enumerate(runs):
|
||||
if MODE != 'parts_a' or not (RANGE[0] <= i < RANGE[1]): continue
|
||||
add(loft(stations_of(a, b), lambda s: EXC_PTS), f"EXC_{i+1:02d}_{pat}", f"굴착 여유대 {pat} {a/1000:.3f}~{b/1000:.3f}",
|
||||
"굴착면", (150, 120, 90), pat, a, b)
|
||||
for i, (a, b, pat) in enumerate(runs): # 기타 구성요소도 지보패턴 구간(run) 경계에서 분할
|
||||
if MODE != 'parts_b' or not (RANGE[0] <= i < RANGE[1]): continue
|
||||
sts = stations_of(a, b)
|
||||
rid = f"{i+1:02d}_{pat}"
|
||||
rng = f"{a/1000:.3f}~{b/1000:.3f}"
|
||||
add(loft(sts, lambda s: np.array(pav_corners(slope_pct(s) / 100.0))), f"PAV_{rid}", f"노면 계획고 포장 {pat} {rng}",
|
||||
"노면(계획고)", COL["pav"], pat, a, b)
|
||||
add(loft(sts, lambda s: base_pts(slope_pct(s) / 100.0)), f"BASE_{rid}", f"기층·인버트(가정) {pat} {rng}",
|
||||
"기층·인버트(가정)", COL["base"], pat, a, b)
|
||||
for side, dpts, pipe in (("L", DRAIN_L_PTS, PIPE_L), ("R", DRAIN_R_PTS, PIPE_R)):
|
||||
bx = loft(sts, lambda s, d=dpts: d)
|
||||
pp = loft(sts, lambda s, p=pipe: PIPE_PTS(p[0], p[1], p[2]))
|
||||
add(bx - pp, f"DRN{side}_{rid}", f"측구 {side} {pat} {rng}", "배수(측구)", COL["drain"], pat, a, b)
|
||||
add(loft(sts, lambda s: HBOX_PTS), f"HBOX_{rid}", f"공동구 H(가정) {pat} {rng}", "공동구(가정)", COL["hi"], pat, a, b)
|
||||
add(loft(sts, lambda s: IBOX_PTS), f"IBOX_{rid}", f"공동구 I(가정) {pat} {rng}", "공동구(가정)", COL["hi"], pat, a, b)
|
||||
|
||||
# 시설한계 50 mm reference lines (wires, not solids)
|
||||
clr = inner_poly.buffer(CLEAR_OFFSET).exterior
|
||||
clr_pts = [(x, z) for x, z in list(clr.coords)[:-1] if z >= chord_z(x) - 1e-9 and abs(x) <= 5.4]
|
||||
idxs = np.round(np.linspace(0, len(clr_pts) - 1, CLEAR_LINES)).astype(int)
|
||||
clear_lines = []
|
||||
sts_all = stations_of(S0, S1)
|
||||
for k, ii in enumerate(idxs):
|
||||
x0, z0 = clr_pts[ii]
|
||||
pts3 = [world(s, x0, z0) for s in sts_all]
|
||||
clear_lines.append(pts3)
|
||||
if MODE != 'parts_b' or RANGE[0] != 0:
|
||||
continue
|
||||
w = Wire.make_polygon([Vector(*p) for p in pts3], close=False)
|
||||
w.label = f"REF_CLEAR_{k+1}"; w.color = Color(1.0, 0.44, 0.0)
|
||||
clear_wires.append(w)
|
||||
meta_c.append({"id": f"REF_CLEAR_{k+1}", "label": f"시설한계 50 mm 참조선 {k+1}", "group": "시설한계(참조선)",
|
||||
"pattern": None, "from_sta": S0, "to_sta": S1, "volume_m3": 0, "color": list(COL["clear"]), "reference": True})
|
||||
|
||||
if MODE in ('parts_a', 'parts_b'):
|
||||
tag = f'lin_exc_{RANGE[0]:02d}' if MODE == 'parts_a' else f'oth_{RANGE[0]:02d}'
|
||||
export_step(Compound(children=solids + clear_wires, label=tag), f"{PARTS}/{tag}.step")
|
||||
json.dump(meta_c, open(f"{PARTS}/{tag}.json", 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
|
||||
print('parts written', tag, len(solids), 'solids', len(clear_wires), 'ref wires')
|
||||
sys.exit(0)
|
||||
# ---- post-processing modes (each call < 180 s, 4 GB RAM) ----
|
||||
import glob
|
||||
TAGS = sorted(os.path.basename(x)[:-5] for x in glob.glob(f"{PARTS}/*.json"))
|
||||
from build123d import Solid as _Solid
|
||||
if MODE == 'mesh': # python build_solids.py ROOT mesh <tag>
|
||||
tag = sys.argv[3]
|
||||
imp = import_step(f"{PARTS}/{tag}.step")
|
||||
items = [x for x in (imp.children or list(imp.solids())) if isinstance(x, _Solid)]
|
||||
pj = [m for m in json.load(open(f"{PARTS}/{tag}.json", encoding='utf-8')) if not m.get('reference')]
|
||||
assert len(items) == len(pj), f"{tag}: {len(items)} solids vs {len(pj)} meta"
|
||||
sc = trimesh.Scene(); tmp = tempfile.mkdtemp(prefix='stl_')
|
||||
for k, (sol, mm) in enumerate(zip(items, pj)):
|
||||
stl = os.path.join(tmp, f"s{k}.stl")
|
||||
export_stl(sol, stl, tolerance=0.002, angular_tolerance=0.2)
|
||||
m = trimesh.load(stl, force='mesh')
|
||||
m.visual.face_colors = np.tile(np.array(list(mm['color']) + [255], dtype=np.uint8), (len(m.faces), 1))
|
||||
sc.add_geometry(m, geom_name=mm['id'])
|
||||
sc.export(f"{PARTS}/{tag}.glb")
|
||||
print('mesh', tag, len(sc.geometry), 'geometries'); sys.exit(0)
|
||||
if MODE == 'step': # python build_solids.py ROOT step
|
||||
allitems = []
|
||||
for tag in TAGS:
|
||||
imp = import_step(f"{PARTS}/{tag}.step")
|
||||
allitems += list(imp.children or list(imp.solids()))
|
||||
os.makedirs(f"{ROOT}/model", exist_ok=True)
|
||||
export_step(Compound(children=allitems, label="본선 4+745~5+480 표준단면 모델"), f"{ROOT}/model/tunnel_solids.step")
|
||||
print('step merged', len(allitems), 'items', os.path.getsize(f"{ROOT}/model/tunnel_solids.step"), 'bytes'); sys.exit(0)
|
||||
if MODE == 'final': # python build_solids.py ROOT final
|
||||
meta_c = []
|
||||
for tag in TAGS:
|
||||
meta_c += json.load(open(f"{PARTS}/{tag}.json", encoding='utf-8'))
|
||||
scene = trimesh.Scene()
|
||||
for tag in TAGS:
|
||||
g = trimesh.load(f"{PARTS}/{tag}.glb")
|
||||
for name, geom in g.geometry.items():
|
||||
scene.add_geometry(geom, geom_name=name)
|
||||
glb_faces = {}
|
||||
for k, pts3 in enumerate(clear_lines): # 시설한계 reference ribbons (visual only; exact wires in STEP)
|
||||
P3 = np.array(pts3); V = []; F = []
|
||||
for i in range(len(P3)):
|
||||
d = P3[min(i + 1, len(P3) - 1)] - P3[max(i - 1, 0)]
|
||||
d = d / (np.linalg.norm(d) + 1e-12)
|
||||
w = np.cross(d, np.array([0, 0, 1.0])); w = w / (np.linalg.norm(w) + 1e-12) * CLEAR_RIB_W
|
||||
V += [P3[i] - w, P3[i] + w]
|
||||
for i in range(len(P3) - 1):
|
||||
a0, b0, c0, d0 = 2 * i, 2 * i + 1, 2 * i + 2, 2 * i + 3
|
||||
F += [[a0, c0, b0], [b0, c0, d0]]
|
||||
m = trimesh.Trimesh(vertices=np.array(V), faces=np.array(F), process=False)
|
||||
m.visual.face_colors = np.tile(np.array([255, 111, 0, 255], dtype=np.uint8), (len(m.faces), 1))
|
||||
scene.add_geometry(m, geom_name=f"REF_CLEAR_{k+1}")
|
||||
glb_path = f"{ROOT}/model/tunnel_solids.glb"
|
||||
scene.export(glb_path)
|
||||
step_path = f"{ROOT}/model/tunnel_solids.step"
|
||||
solid_meta = [x for x in meta_c if not x.get('reference')]
|
||||
meta = {
|
||||
"model": ["tunnel_solids.step (STEP B-rep, Rhino/Autodesk)", "tunnel_solids.glb (web viewer, same B-rep tessellated)"],
|
||||
"scope": f"{S0}~{S1} (4+745~5+480)", "units": "m",
|
||||
"origin_plan_xy": [round(ORX, 3), round(ORY, 3)],
|
||||
"sections": {"lining": "rev2: 라이닝 R6982/R7282, 하단 호 도면 끝점까지", "excavation": "굴착 여유대 R7402/R4574/R4819 (노면 위)",
|
||||
"road": "계획고 노면 포장 280 mm, 측점별 편경사 S (노면만)", "base": "기층·인버트: 가정 (도면 미표현)",
|
||||
"drains": "측구 300x836 mm, 관 r150", "hi": "H/I 박스: 공동구 가정(사용자 결정)", "clearance": "시설한계 50 mm 참조선 (솔리드 아님)"},
|
||||
"assumptions": ["기층·인버트 형상·두께는 가정 (도면 미표현, 사용자 결정 '가정하여 생성')",
|
||||
"H/I 박스 = 공동구 가정 (사용자 결정)",
|
||||
"편경사 부호 z = (S/100)·x, x = 접선 좌측 법선 (부호 확인 필요)",
|
||||
"라이닝·굴착 여유대는 대칭 (편경사 미적용)",
|
||||
"측점 간 축선은 선형 보간, 단면은 10 m 간격 Ruled loft (곡선부 근사)",
|
||||
"라이닝 H/I 박스 제외 부분은 3D 불리언 차감 (공동구 가정)"],
|
||||
"pattern_runs": [{"id": f"LIN_{i+1:02d}_{p}", "pattern": p, "from": a, "to": b} for i, (a, b, p) in enumerate(runs)],
|
||||
"components": meta_c,
|
||||
"open": ["E-1, E-2, RP-2, WL-3 패턴 배치 [미확정] (당진 표 밖)", "NP-5BE-1-WL-4 해석 [확인 필요]",
|
||||
"04 공동구·배수 도면 상세 미반영 (측구·공동구 치수는 03-001 단면 기준)", "편경사 부호(좌·우) 확인 필요"]
|
||||
}
|
||||
json.dump(meta, open(f"{ROOT}/model/tunnel_solids_meta.json", "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
||||
ver = {"solid_count": len(solid_meta), "runs": len(runs), "pattern_coverage": f"{runs[0][0]}..{runs[-1][1]} contiguous",
|
||||
"lining_runs": sum(1 for x in solid_meta if x['group'] == '라이닝'),
|
||||
"excavation_runs": sum(1 for x in solid_meta if x['group'] == '굴착면'),
|
||||
"volume_sum_m3": round(sum(x['volume_m3'] for x in solid_meta), 3),
|
||||
"step_bytes": os.path.getsize(step_path), "glb_bytes": os.path.getsize(glb_path),
|
||||
"glb_geometries": len(trimesh.load(glb_path).geometry)}
|
||||
def _overlap(sample):
|
||||
base = Polygon(base_pts(slope_pct(sample) / 100.0)); pav = Polygon(pav_corners(slope_pct(sample) / 100.0))
|
||||
return {"lining_vs_excavation": round(LINING_POLY.intersection(EXCAV_POLY).area, 9),
|
||||
"base_vs_pavement": round(base.intersection(pav).area, 9),
|
||||
"lining_vs_base": round(LINING_POLY.intersection(base).area, 9),
|
||||
"pavement_vs_lining": round(pav.intersection(LINING_POLY).area, 9),
|
||||
"base_vs_drainL": round(base.intersection(DRAIN_L).area, 9),
|
||||
"base_vs_drainR": round(base.intersection(DRAIN_R).area, 9)}
|
||||
ver["section_overlap_m2_by_station"] = {str(st): _overlap(st) for st in [4745, 5105, 5165, 5480]}
|
||||
json.dump(ver, open(f"{ROOT}/data/verify_solids.json", "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
||||
print(json.dumps(ver, ensure_ascii=False, indent=1)); sys.exit(0)
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare two plan sheets layer-by-layer and list elevation-like numeric
|
||||
texts (decimal values in 0..1000) per layer, to locate ground levels (지반고).
|
||||
Read-only on the source DXF."""
|
||||
import collections
|
||||
import re
|
||||
import sys
|
||||
|
||||
import ezdxf
|
||||
|
||||
num = re.compile(r"^-?\d{1,4}\.\d{1,3}$")
|
||||
|
||||
|
||||
def scan(path):
|
||||
msp = ezdxf.readfile(path).modelspace()
|
||||
layers = collections.Counter(e.dxf.layer for e in msp)
|
||||
el = collections.defaultdict(list)
|
||||
for e in msp.query("TEXT"):
|
||||
s = e.dxf.text.strip()
|
||||
if num.match(s) and 0 <= float(s) <= 1000:
|
||||
el[e.dxf.layer].append((round(e.dxf.insert.x, 1), round(e.dxf.insert.y, 1), s))
|
||||
return layers, el
|
||||
|
||||
la, ea = scan(sys.argv[1])
|
||||
lb, eb = scan(sys.argv[2])
|
||||
print("layers only in 001:", sorted(set(la) - set(lb)))
|
||||
print("layers only in 002:", sorted(set(lb) - set(la)))
|
||||
for lay in sorted(set(la) | set(lb)):
|
||||
if la[lay] != lb[lay]:
|
||||
print(f" count diff {lay}: 001={la[lay]} 002={lb[lay]}")
|
||||
print("== elevation-like numeric texts (layer: n, sample)")
|
||||
for name, el in (("001", ea), ("002", eb)):
|
||||
for lay, items in sorted(el.items(), key=lambda kv: -len(kv[1])):
|
||||
print(f" {name} {lay}: n={len(items)} sample={sorted(items)[:6]}")
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Task 4: dump the variable table of the standard section (C0060303-001) as a
|
||||
coordinate grid. Rows = cross slope (S, 편경사) per user answer 2026-10-21; the
|
||||
first row = variable names. Every TEXT on the table layers is kept (no regex
|
||||
filter), so that the S column and the header row can be checked."""
|
||||
import csv
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
import ezdxf
|
||||
|
||||
SRC, OUT = sys.argv[1], sys.argv[2]
|
||||
msp = ezdxf.readfile(SRC).modelspace()
|
||||
cells = [(round(e.dxf.insert.x, 1), round(e.dxf.insert.y, 1), e.dxf.text.strip())
|
||||
for e in msp.query("TEXT") if e.dxf.layer in ("CS-TABL-TEX1", "CS-TABL-TEX2")]
|
||||
rows = defaultdict(list)
|
||||
for x, y, s in cells:
|
||||
rows[round(y / 50) * 50].append((x, s)) # bucket by ~50 mm
|
||||
with open(OUT, "w", newline="", encoding="utf-8") as fh:
|
||||
w = csv.writer(fh)
|
||||
for y in sorted(rows, reverse=True):
|
||||
line = [s for x, s in sorted(rows[y])]
|
||||
w.writerow([y] + line)
|
||||
print(f"Y{y:>7}: " + " | ".join(f"{x:.0f}:{s}" for x, s in sorted(rows[y])))
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Task 3 exploration: layer usage and long polylines / text clusters in a
|
||||
plan-profile DXF, to locate the tunnel centreline (본선 축선) candidates."""
|
||||
import collections
|
||||
import json
|
||||
import sys
|
||||
|
||||
import ezdxf
|
||||
|
||||
path = sys.argv[1]
|
||||
doc = ezdxf.readfile(path)
|
||||
msp = doc.modelspace()
|
||||
by_layer = collections.Counter(e.dxf.layer for e in msp)
|
||||
print("layers used:", len(by_layer))
|
||||
for name, n in by_layer.most_common(25):
|
||||
print(f" {n:6d} {name}")
|
||||
# long polylines = candidate alignment / outline strings
|
||||
polys = []
|
||||
for e in msp:
|
||||
if e.dxftype() == "LWPOLYLINE":
|
||||
pts = list(e.get_points("xy"))
|
||||
if len(pts) >= 20:
|
||||
xs = [p[0] for p in pts]; ys = [p[1] for p in pts]
|
||||
polys.append({"layer": e.dxf.layer, "n": len(pts),
|
||||
"x": [round(min(xs), 1), round(max(xs), 1)],
|
||||
"y": [round(min(ys), 1), round(max(ys), 1)]})
|
||||
polys.sort(key=lambda d: -d["n"])
|
||||
print("long LWPOLYLINE (>=20 vertices):", len(polys))
|
||||
for p in polys[:12]:
|
||||
print(" ", p)
|
||||
# text that looks like station (STA) or level (EL) labels
|
||||
import re
|
||||
pat = re.compile(r"(STA|No\.?|EL|E\.L|\d+\+\d+|\+\d{2,3}\.\d{3}|\d{2,3}\.\d{2,3})", re.I)
|
||||
hits = []
|
||||
for e in msp:
|
||||
if e.dxftype() in ("TEXT", "MTEXT"):
|
||||
s = e.dxf.text if e.dxftype() == "TEXT" else e.plain_text()
|
||||
if s and pat.search(s):
|
||||
loc = e.dxf.insert if e.dxftype() == "TEXT" else e.dxf.insert
|
||||
hits.append((round(loc.x, 1), round(loc.y, 1), s.strip()[:40]))
|
||||
print("station/level-like texts:", len(hits))
|
||||
for h in sorted(hits)[:25]:
|
||||
print(" ", h)
|
||||
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Explore the concrete-lining structure drawings (04-004 .. 04-009).
|
||||
|
||||
Read-only on the source RAG folder. For each DXF prints layers with entity
|
||||
counts, INSERT block names, dimension measurements and every text string with
|
||||
its position, and writes a JSON dump for later use.
|
||||
Usage: explore_lining.py <RAG_04_DIR> <OUT_JSON> [max_texts_per_file]
|
||||
"""
|
||||
import collections
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
import ezdxf
|
||||
from ezdxf import recover
|
||||
|
||||
RAG_04 = sys.argv[1]
|
||||
OUT_JSON = sys.argv[2]
|
||||
MAX_TEXT = int(sys.argv[3]) if len(sys.argv) > 3 else 400
|
||||
|
||||
|
||||
def load(path):
|
||||
try:
|
||||
return ezdxf.readfile(path), None
|
||||
except Exception as exc:
|
||||
try:
|
||||
doc, _ = recover.readfile(path)
|
||||
return doc, f"recovered after strict error: {type(exc).__name__}"
|
||||
except Exception as exc2:
|
||||
return None, f"{type(exc).__name__} | recover: {type(exc2).__name__}: {exc2}"
|
||||
|
||||
|
||||
def explore(path):
|
||||
rec = {"file": os.path.basename(path)}
|
||||
doc, note = load(path)
|
||||
if doc is None:
|
||||
rec["error"] = note
|
||||
return rec
|
||||
rec["note"] = note
|
||||
msp = doc.modelspace()
|
||||
layers = collections.Counter()
|
||||
counts = collections.Counter()
|
||||
for e in msp:
|
||||
counts[e.dxftype()] += 1
|
||||
layers[e.dxf.layer] += 1
|
||||
rec["layers"] = dict(layers.most_common())
|
||||
rec["entity_counts"] = dict(counts.most_common())
|
||||
rec["inserts"] = dict(collections.Counter(e.dxf.name for e in msp if e.dxftype() == "INSERT").most_common(30))
|
||||
dims, texts = [], []
|
||||
for e in msp:
|
||||
t = e.dxftype()
|
||||
if t == "DIMENSION":
|
||||
try:
|
||||
val = round(float(e.get_measurement()), 3)
|
||||
except Exception:
|
||||
val = None
|
||||
dims.append({"layer": e.dxf.layer, "value": val, "text": e.dxf.get("text", "")})
|
||||
elif t in ("TEXT", "MTEXT"):
|
||||
s = e.dxf.text if t == "TEXT" else e.plain_text()
|
||||
if s and s.strip():
|
||||
pos = e.dxf.insert
|
||||
texts.append({"layer": e.dxf.layer, "x": round(pos.x, 2), "y": round(pos.y, 2),
|
||||
"text": s.strip().replace("\n", " | ")[:200]})
|
||||
rec["dimensions"] = dims[:200]
|
||||
rec["texts_total"] = len(texts)
|
||||
rec["texts"] = texts[:MAX_TEXT]
|
||||
return rec
|
||||
|
||||
|
||||
def main():
|
||||
files = sorted(glob.glob(os.path.join(RAG_04, "C0060304-00[4-9]*.dxf")))
|
||||
out = [explore(f) for f in files]
|
||||
with open(OUT_JSON, "w", encoding="utf-8") as fh:
|
||||
json.dump(out, fh, ensure_ascii=False, indent=1)
|
||||
for r in out:
|
||||
print("==", r["file"])
|
||||
if "error" in r:
|
||||
print(" ERROR", r["error"])
|
||||
continue
|
||||
print(" note:", r["note"])
|
||||
print(" entities:", r["entity_counts"])
|
||||
print(" layers:", dict(list(r["layers"].items())[:25]))
|
||||
print(" inserts:", r["inserts"])
|
||||
print(" dims:", len(r["dimensions"]), "texts:", r["texts_total"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Task 4 exploration: layers, arcs (radius + centre) and dimension-like texts
|
||||
of the standard section drawing (C0060303-001). Read-only."""
|
||||
import collections
|
||||
import re
|
||||
import sys
|
||||
|
||||
import ezdxf
|
||||
from ezdxf import bbox
|
||||
|
||||
msp = ezdxf.readfile(sys.argv[1]).modelspace()
|
||||
print("layers:", collections.Counter(e.dxf.layer for e in msp).most_common(12))
|
||||
ext = bbox.extents(msp)
|
||||
print("extents:", [round(v, 1) for v in ext.extmin], [round(v, 1) for v in ext.extmax])
|
||||
print("== ARCs (layer, centre, radius, start/end deg) sorted by radius")
|
||||
arcs = sorted(msp.query("ARC"), key=lambda e: -e.dxf.radius)
|
||||
for e in arcs[:14]:
|
||||
c = e.dxf.center
|
||||
print(f" {e.dxf.layer:14s} c=({c.x:9.2f},{c.y:9.2f}) R={e.dxf.radius:9.2f} {e.dxf.start_angle:7.1f}->{e.dxf.end_angle:7.1f}")
|
||||
print("== R-labels and numeric dims")
|
||||
pat = re.compile(r"^(R\d'?|\d{2,5}(\.\d+)?|\d+\.\d+%?|-?\d+\.\d+)$")
|
||||
for e in msp.query("TEXT"):
|
||||
s = e.dxf.text.strip()
|
||||
if pat.match(s) or s.startswith("R"):
|
||||
print(f" {e.dxf.layer:14s} ({e.dxf.insert.x:9.2f},{e.dxf.insert.y:9.2f}) {s}")
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Task 3 (HITL draft): main-line centreline station table from a plan sheet.
|
||||
|
||||
Method (layers CA-ALGN-TEXT labels and CA-ALGN-STSM station ticks):
|
||||
1. Station labels: 'N+mmm' is explicit; bare numbers take the km of the next
|
||||
explicit label when they precede it (700 before 5+000 -> 4+700), otherwise
|
||||
the km of the previous explicit label.
|
||||
2. Each station has two labels (one per side of the road). Each label is
|
||||
paired with its nearest tick; the centreline point is the midpoint of the
|
||||
two tick points.
|
||||
3. Checks reported (not forced): side offset per station, and consecutive
|
||||
centreline distance vs station difference (residual).
|
||||
DRAFT for HITL review. No value is adjusted to fit."""
|
||||
import csv
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
import ezdxf
|
||||
|
||||
SRC, OUT = sys.argv[1], sys.argv[2]
|
||||
msp = ezdxf.readfile(SRC).modelspace()
|
||||
explicit_re = re.compile(r"^(\d+)\+(\d{3})$")
|
||||
|
||||
labels = [(e.dxf.insert.x, e.dxf.insert.y, e.dxf.text.strip())
|
||||
for e in msp.query("TEXT") if e.dxf.layer == "CA-ALGN-TEXT"]
|
||||
ticks = []
|
||||
for e in msp.query("LINE"):
|
||||
if e.dxf.layer == "CA-ALGN-STSM":
|
||||
a, b = e.dxf.start, e.dxf.end
|
||||
ticks.append(((a.x + b.x) / 2, (a.y + b.y) / 2))
|
||||
|
||||
labels = [l for l in labels if explicit_re.match(l[2]) or l[2].isdigit()]
|
||||
labels.sort(key=lambda t: -t[1]) # along the sheet, NNE -> SSW
|
||||
explicit_idx = [i for i, l in enumerate(labels) if explicit_re.match(l[2])]
|
||||
station_of = {}
|
||||
for i, (x, y, s) in enumerate(labels):
|
||||
if explicit_re.match(s):
|
||||
m = explicit_re.match(s)
|
||||
station_of[i] = int(m.group(1)) * 1000 + int(m.group(2))
|
||||
continue
|
||||
v = int(s)
|
||||
nxt = next((j for j in explicit_idx if j > i), None)
|
||||
prv = next((j for j in reversed(explicit_idx) if j < i), None)
|
||||
if prv is not None:
|
||||
km = int(explicit_re.match(labels[prv][2]).group(1))
|
||||
else:
|
||||
km = int(explicit_re.match(labels[nxt][2]).group(1)) - 1
|
||||
station_of[i] = km * 1000 + v
|
||||
|
||||
groups = defaultdict(list)
|
||||
for i, (x, y, s) in enumerate(labels):
|
||||
groups[station_of[i]].append((x, y))
|
||||
|
||||
|
||||
def nearest_tick(pt):
|
||||
return min(ticks, key=lambda t: math.hypot(t[0] - pt[0], t[1] - pt[1]))
|
||||
|
||||
centre = []
|
||||
for sta in sorted(groups):
|
||||
pts = groups[sta]
|
||||
if len(pts) != 2:
|
||||
continue
|
||||
t1, t2 = nearest_tick(pts[0]), nearest_tick(pts[1])
|
||||
side_gap = math.dist(t1, t2)
|
||||
mid = ((t1[0] + t2[0]) / 2, (t1[1] + t2[1]) / 2)
|
||||
centre.append((sta, mid[0], mid[1], side_gap))
|
||||
|
||||
with open(OUT, "w", newline="", encoding="utf-8") as fh:
|
||||
w = csv.writer(fh)
|
||||
w.writerow(["station_m", "centre_x", "centre_y", "side_gap_m"])
|
||||
for sta, x, y, g in centre:
|
||||
w.writerow([sta, round(x, 3), round(y, 3), round(g, 3)])
|
||||
|
||||
print(f"stations={len(centre)} (labels={len(labels)}, ticks={len(ticks)})")
|
||||
prev = None
|
||||
for sta, x, y, g in centre:
|
||||
step = "" if prev is None else f" d={math.dist(prev[1:3], (x, y)):8.3f} dSTA={sta - prev[0]:5d} res={math.dist(prev[1:3], (x, y)) - (sta - prev[0]):+.3f}"
|
||||
print(f" STA {sta:>5} ({x:12.3f}, {y:12.3f}) side_gap={g:6.2f}{step}")
|
||||
prev = (sta, x, y)
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract structured lining-reinforcement data from the concrete-lining
|
||||
structure drawings 04-004..009 (read-only on RAG).
|
||||
|
||||
Per sheet: pattern groups and their detail kind (입출구부 50 m / 온도미고려),
|
||||
note-5 scope sentence, concrete and rebar grade, rebar sizes present,
|
||||
radius and lap dimensions as printed, EL datum texts.
|
||||
Output: data/lining_spec.json (+ printed summary).
|
||||
Usage: extract_lining_spec.py <RAG_04_DIR> <OUT_JSON>
|
||||
"""
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import ezdxf
|
||||
from ezdxf import recover
|
||||
|
||||
RAG_04, OUT = sys.argv[1], sys.argv[2]
|
||||
BAR_RE = re.compile(r"^0?H(\d{2})$")
|
||||
PAT_RE = re.compile(r"\(([^()]*?)\s*패턴\)")
|
||||
GROUP_RE = re.compile(r"주철근 조립도\(([^()]*?)\s*패턴\)")
|
||||
|
||||
|
||||
def load(path):
|
||||
try:
|
||||
return ezdxf.readfile(path)
|
||||
except Exception:
|
||||
doc, _ = recover.readfile(path)
|
||||
return doc
|
||||
|
||||
|
||||
def texts_of(doc):
|
||||
out = []
|
||||
for e in doc.modelspace():
|
||||
if e.dxftype() == "TEXT":
|
||||
s = e.dxf.text
|
||||
elif e.dxftype() == "MTEXT":
|
||||
s = e.plain_text()
|
||||
else:
|
||||
continue
|
||||
if s and s.strip():
|
||||
out.append((e.dxf.layer, round(e.dxf.insert.x, 1), round(e.dxf.insert.y, 1), s.strip()))
|
||||
return out
|
||||
|
||||
|
||||
def sheet_no(name):
|
||||
return int(re.search(r"C0060304-(\d{3})", name).group(1))
|
||||
|
||||
|
||||
def main():
|
||||
files = sorted(glob.glob(os.path.join(RAG_04, "C0060304-00[4-9]*.dxf")))
|
||||
sheets = []
|
||||
for f in files:
|
||||
doc = load(f)
|
||||
tx = texts_of(doc)
|
||||
rec = {"sheet": sheet_no(f), "file": os.path.basename(f)}
|
||||
# pattern groups: detail kind from the text itself
|
||||
groups = []
|
||||
for layer, x, y, s in tx:
|
||||
if layer != "CS-TABL-TEX1":
|
||||
continue
|
||||
if "패턴" in s:
|
||||
kind = "입출구부 50m" if "입출구부" in s else ("온도미고려" if "온도미고려" in s else "기타")
|
||||
m = PAT_RE.search(s)
|
||||
groups.append({"group": m.group(1) if m else None, "kind": kind, "raw": s})
|
||||
rec["pattern_details"] = groups
|
||||
# scope note 5 (full line, joined with the continuation line)
|
||||
note5 = [s for _, _, _, s in tx if s.startswith("5. 본 콘크리트 라이닝 구조도")]
|
||||
cont = [s for _, _, _, s in tx if s.startswith("설계조건과 상이할 경우")]
|
||||
rec["note5"] = (note5[0] + " " + (cont[0] if cont else "")).strip() if note5 else None
|
||||
rec["concrete"] = sorted({s for _, _, _, s in tx if "fck" in s})
|
||||
rec["rebar_grade"] = sorted({s for _, _, _, s in tx if "fy=" in s})
|
||||
rec["aggregate"] = sorted({s for _, _, _, s in tx if "최대치수" in s})
|
||||
bars = {}
|
||||
for layer, x, y, s in tx:
|
||||
m = BAR_RE.match(s)
|
||||
if m:
|
||||
bars[f"H{m.group(1)}"] = bars.get(f"H{m.group(1)}", 0) + 1
|
||||
rec["rebar_sizes_label_count"] = dict(sorted(bars.items()))
|
||||
rec["radius_dims"] = sorted({s for layer, _, _, s in tx if layer == "CS-DIMT" and re.match(r"R\d?'?=", s)})
|
||||
rec["lap_dims"] = sorted({s for layer, _, _, s in tx if layer == "CS-DIMT" and "mm" in s and "이음장" not in s and "600" in s})
|
||||
rec["el_texts"] = sorted({s for _, _, _, s in tx if s.startswith("EL")})
|
||||
rec["entrance_exit_50m"] = any("입출구부 50m" in s for _, _, _, s in tx)
|
||||
sheets.append(rec)
|
||||
with open(OUT, "w", encoding="utf-8") as fh:
|
||||
json.dump({"source": "C0060304-004..009 (콘크리트라이닝구조도)", "sheets": sheets},
|
||||
fh, ensure_ascii=False, indent=1)
|
||||
for r in sheets:
|
||||
print("==", r["sheet"], "| groups:", [(g["group"], g["kind"]) for g in r["pattern_details"]])
|
||||
print(" note5:", (r["note5"] or "")[:140])
|
||||
print(" concrete:", r["concrete"], "| rebar:", r["rebar_grade"], "| agg:", r["aggregate"])
|
||||
print(" bars:", r["rebar_sizes_label_count"], "| R:", r["radius_dims"], "| EL:", r["el_texts"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Task 5: support-pattern (지보패턴) station ranges from drawings 03-003..008.
|
||||
Collects every TEXT that names a station (STA. / N+mmm / N+mmm.mm) and every
|
||||
pattern name (NP-...) with its layer and position, without interpreting ranges."""
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import ezdxf
|
||||
|
||||
RAG = sys.argv[1]
|
||||
pat_re = re.compile(r"NP-\d[A-Z0-9-]*|WL-\d")
|
||||
sta_re = re.compile(r"(STA\.?\s*)?\d{1,2}\+\d{2,3}(\.\d{2})?")
|
||||
files = sorted(glob.glob(os.path.join(RAG, "03_본선터널굴착", "C0060303-00[3-8]*.dxf")))
|
||||
for f in files:
|
||||
msp = ezdxf.readfile(f).modelspace()
|
||||
stas, pats = [], set()
|
||||
for e in msp.query("TEXT MTEXT"):
|
||||
s = e.dxf.text if e.dxftype() == "TEXT" else e.plain_text()
|
||||
for m in sta_re.finditer(s):
|
||||
stas.append((round(e.dxf.insert.x), round(e.dxf.insert.y), m.group(0).strip()))
|
||||
for m in pat_re.finditer(s):
|
||||
pats.add(m.group(0))
|
||||
print(os.path.basename(f)[:28], "| patterns:", sorted(pats))
|
||||
for x, y, t in sorted(stas, key=lambda r: (r[0], r[1]))[:12]:
|
||||
print(f" ({x},{y}) {t}")
|
||||
print(f" station-like texts: {len(stas)}")
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract the 당진방향 station pattern table from 참고_지보패턴현황 (read-only).
|
||||
Rows 55..96 of the sheet: C=start STA, G=end STA, J=length, M=lining pattern
|
||||
(지보패턴), O=실시설계 excavation class (blank = continues the row above),
|
||||
Q=굴착공법, T=비고. Also extracts the 대산방향 table (rows 13..49) for reference.
|
||||
Usage: extract_pattern_table.py <xlsx> <out_dir>"""
|
||||
import csv, os, sys
|
||||
import openpyxl
|
||||
|
||||
SRC, OUT = sys.argv[1], sys.argv[2]
|
||||
wb = openpyxl.load_workbook(SRC, read_only=True, data_only=True)
|
||||
ws = wb["참고_지보패턴현황"]
|
||||
rows = {r: row for r, row in enumerate(ws.iter_rows(min_row=1, max_row=114, max_col=33, values_only=True), start=1)}
|
||||
|
||||
|
||||
def table(r0, r1, tag):
|
||||
out, last_o = [], None
|
||||
for r in range(r0, r1 + 1):
|
||||
row = rows[r]
|
||||
try:
|
||||
a, b = float(row[2]), float(row[6])
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
m = (row[12] or "").strip() if isinstance(row[12], str) else row[12]
|
||||
o_raw = row[14]
|
||||
o = o_raw if o_raw not in (None, "") else last_o
|
||||
last_o = o
|
||||
remark = (row[19] or "") if isinstance(row[19], str) else ""
|
||||
out.append({"direction": tag, "source_row": r, "sta_from": a, "sta_to": b,
|
||||
"length_m": row[9], "lining_pattern": m or ("개착부" if "개착" in remark else ""),
|
||||
"excavation": o or "", "excavation_inherited": o_raw in (None, ""),
|
||||
"method": (row[16] or "") if isinstance(row[16], str) else "",
|
||||
"remark": remark})
|
||||
return out
|
||||
|
||||
|
||||
south = table(55, 96, "당진")
|
||||
north = table(13, 49, "대산")
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
cols = list(south[0].keys())
|
||||
for name, data in [("pattern_stations_dangjin.csv", south), ("pattern_stations_daesan_ref.csv", north)]:
|
||||
with open(os.path.join(OUT, name), "w", encoding="utf-8", newline="") as fh:
|
||||
w = csv.DictWriter(fh, fieldnames=cols)
|
||||
w.writeheader()
|
||||
w.writerows(data)
|
||||
print("dangjin rows", len(south), "range", south[0]["sta_from"], "->", south[-1]["sta_to"])
|
||||
print("daesan rows", len(north), "range", north[0]["sta_from"], "->", north[-1]["sta_to"])
|
||||
print("blank lining pattern rows (dangjin):", [(d["source_row"], d["remark"]) for d in south if not d["lining_pattern"]])
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Task 3 (user-confirmed reading): ground-level and design-grade values per
|
||||
20 m station from sheet 02-002 (당진방향).
|
||||
|
||||
Confirmed by the user (2026-10-21): decimal texts in CR-TABL-TEX2 are 지반고 (GL),
|
||||
sheet 002 values used; the ruler 740.00..980.00 is chainage 4+740..4+980, and
|
||||
0.00 is 5+000 (ruler continues in 20 m steps).
|
||||
Method: ruler texts give anchors (chainage = 4000+v for 740..980; 5000+v after 0.00).
|
||||
Each decimal text is projected onto the ruler axis; its station is the nearest anchor.
|
||||
Two decimal series appear at each station (|across| ~ 17-21 and ~ 32-39 m);
|
||||
the series names are NOT assigned here (HITL). Checks are reported, not forced."""
|
||||
import csv
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
|
||||
import ezdxf
|
||||
|
||||
SRC, OUT = sys.argv[1], sys.argv[2]
|
||||
msp = ezdxf.readfile(SRC).modelspace()
|
||||
texts = [(e.dxf.insert.x, e.dxf.insert.y, e.dxf.text.strip())
|
||||
for e in msp.query("TEXT") if e.dxf.layer == "CR-TABL-TEX2"]
|
||||
|
||||
ruler = {}
|
||||
for x, y, s in texts:
|
||||
if re.fullmatch(r"\d{1,3}\.00", s):
|
||||
ruler[int(s.split(".")[0])] = (x, y)
|
||||
anchors = {}
|
||||
for v in range(740, 1000, 20):
|
||||
if v in ruler:
|
||||
anchors[4000 + v] = ruler[v]
|
||||
for v in range(0, 1000, 20):
|
||||
if v in ruler:
|
||||
anchors[5000 + v] = ruler[v]
|
||||
else:
|
||||
break
|
||||
stas = sorted(anchors)
|
||||
print("anchors:", len(stas), "range", stas[0], "..", stas[-1])
|
||||
gaps = [(stas[i + 1] - stas[i], round(math.dist(anchors[stas[i]], anchors[stas[i + 1]]), 2))
|
||||
for i in range(len(stas) - 1)]
|
||||
print("consecutive (dSTA, chord m):", gaps)
|
||||
|
||||
A0, A1 = anchors[stas[0]], anchors[stas[-1]]
|
||||
L = math.dist(A0, A1)
|
||||
u = ((A1[0] - A0[0]) / L, (A1[1] - A0[1]) / L)
|
||||
v_ = (-u[1], u[0])
|
||||
rows = []
|
||||
for x, y, s in texts:
|
||||
if not re.fullmatch(r"\d{1,3}\.\d{3}", s):
|
||||
continue
|
||||
dx, dy = x - A0[0], y - A0[1]
|
||||
along = dx * u[0] + dy * u[1]
|
||||
across = dx * v_[0] + dy * v_[1]
|
||||
if not (-5.0 <= along <= L + 5.0 and abs(across) < 60):
|
||||
continue # outside the ruler band (table noise)
|
||||
sta_est = stas[0] + along * (stas[-1] - stas[0]) / L
|
||||
near = min(stas, key=lambda k: abs(k - sta_est))
|
||||
rows.append((near, round(sta_est, 1), round(across, 2), float(s), round(x, 2), round(y, 2)))
|
||||
|
||||
with open(OUT, "w", newline="", encoding="utf-8") as fh:
|
||||
w = csv.writer(fh)
|
||||
w.writerow(["station_anchor", "est_station", "across_m", "value", "x", "y"])
|
||||
w.writerows(sorted(rows))
|
||||
per = {}
|
||||
for near, est, acr, val, _, _ in rows:
|
||||
per.setdefault(near, []).append((acr, val))
|
||||
for k in sorted(per):
|
||||
print(f"STA {k}: " + " ".join(f"[{a:+.1f}m {val:.3f}]" for a, val in sorted(per[k])))
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Inventory the main-line (본선) DXF drawings: header, layers, entity counts,
|
||||
extents, 3D flag and text samples. Read-only on the source RAG folder."""
|
||||
import collections
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
import ezdxf
|
||||
from ezdxf import bbox, recover
|
||||
|
||||
RAG_DIR = sys.argv[1]
|
||||
OUT_JSON = sys.argv[2]
|
||||
# 본선 scope (approved 2026-10-21): 02 평면·종단, 03 본선굴착, 04 본선기타
|
||||
SUBDIRS = ["02_평면 및 종단면도", "03_본선터널굴착", "04_본선터널기타"]
|
||||
|
||||
|
||||
def inventory(path):
|
||||
rec = {"file": os.path.basename(path), "ok": False}
|
||||
rec["recovered"] = False
|
||||
try:
|
||||
doc = ezdxf.readfile(path)
|
||||
except Exception as exc: # strict load failed: retry in recover mode and record it
|
||||
strict_error = f"{type(exc).__name__}: {exc}"
|
||||
try:
|
||||
doc, _auditor = recover.readfile(path)
|
||||
rec["recovered"] = True
|
||||
rec["strict_error"] = strict_error
|
||||
except Exception as exc2:
|
||||
rec["error"] = f"{strict_error} | recover: {type(exc2).__name__}: {exc2}"
|
||||
return rec
|
||||
hdr = doc.header
|
||||
rec["dxfversion"] = doc.dxfversion
|
||||
rec["insunits"] = hdr.get("$INSUNITS")
|
||||
rec["codepage"] = hdr.get("$DWGCODEPAGE")
|
||||
rec["layers"] = len(doc.layers)
|
||||
msp = doc.modelspace()
|
||||
counts = collections.Counter(e.dxftype() for e in msp)
|
||||
rec["entity_counts"] = dict(counts.most_common())
|
||||
rec["blocks"] = len([b for b in doc.blocks if not b.name.startswith("*")])
|
||||
z_vals = []
|
||||
texts = []
|
||||
for e in msp:
|
||||
t = e.dxftype()
|
||||
if t == "LINE":
|
||||
z_vals += [e.dxf.start.z, e.dxf.end.z]
|
||||
elif t in ("TEXT", "MTEXT"):
|
||||
s = e.dxf.text if t == "TEXT" else e.plain_text()
|
||||
if s and len(texts) < 40:
|
||||
texts.append(s.strip()[:80])
|
||||
elif t in ("POINT", "INSERT", "CIRCLE", "ARC"):
|
||||
z_vals.append(e.dxf.location.z if t == "POINT" else e.dxf.insert.z if t == "INSERT" else e.dxf.center.z)
|
||||
rec["z_min"] = round(min(z_vals), 3) if z_vals else None
|
||||
rec["z_max"] = round(max(z_vals), 3) if z_vals else None
|
||||
rec["has_3d_geometry"] = bool(z_vals) and (max(z_vals) - min(z_vals)) > 1e-6
|
||||
try:
|
||||
ext = bbox.extents(msp)
|
||||
rec["extents_min"] = [round(v, 2) for v in ext.extmin]
|
||||
rec["extents_max"] = [round(v, 2) for v in ext.extmax]
|
||||
except Exception as exc:
|
||||
rec["extents_error"] = str(exc)
|
||||
rec["text_sample"] = texts
|
||||
rec["ok"] = True
|
||||
return rec
|
||||
|
||||
|
||||
def main():
|
||||
paths = []
|
||||
for sub in SUBDIRS:
|
||||
paths += sorted(glob.glob(os.path.join(RAG_DIR, sub, "*.dxf")))
|
||||
results = [inventory(p) for p in paths]
|
||||
os.makedirs(os.path.dirname(OUT_JSON), exist_ok=True)
|
||||
with open(OUT_JSON, "w", encoding="utf-8") as fh:
|
||||
json.dump(results, fh, ensure_ascii=False, indent=2)
|
||||
ok = sum(r["ok"] for r in results)
|
||||
print(f"files={len(results)} ok={ok} failed={len(results) - ok}")
|
||||
for r in results:
|
||||
status = "OK " if r["ok"] else "ERR"
|
||||
detail = r.get("error") or f"ents={sum(r['entity_counts'].values())} 3D={r['has_3d_geometry']} units={r['insunits']} cp={r['codepage']} recovered={r['recovered']}"
|
||||
print(f"{status} {r['file'][:60]:60s} {detail}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render the Task 3 draft centreline report (DOC/03_설계) from the centreline
|
||||
CSVs written by extract_alignment_plan.py. Numbers are read, not typed."""
|
||||
import csv
|
||||
import math
|
||||
import sys
|
||||
|
||||
CSV_001, CSV_002, OUT_MD = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
|
||||
|
||||
def load(path):
|
||||
with open(path, encoding="utf-8") as fh:
|
||||
return [(int(r["station_m"]), float(r["centre_x"]), float(r["centre_y"]), float(r["side_gap_m"]))
|
||||
for r in csv.DictReader(fh)]
|
||||
|
||||
a, b = load(CSV_001), load(CSV_002)
|
||||
same = a == b
|
||||
lines = ["# 본선 중심선 측점 좌표 (초안)\n",
|
||||
"| 버전 | 일자 | 변경 내용 |", "|---|---|---|",
|
||||
"| v0.1.00 | 2026-10-21 | 최초 작성. 평면도 02-001 기준 측점 4+700~5+500 중심선 초안 (HITL 검토 대상) |\n",
|
||||
"## 1. 추출 방법",
|
||||
"- 소스: 레이어 `CA-ALGN-TEXT`(측점 라벨), `CA-ALGN-STSM`(측점 눈금) [출처: C0060302-001]",
|
||||
"- 측점 라벨은 측점당 2개(좌우 측). 각 라벨의 최근접 눈금 2점의 **중점**을 중심선 점으로 채택 (정의 HITL 확인 대상)",
|
||||
"- 좌표는 도면 좌표계 그대로 (단위: 도면 표기 따름, 사용자 승인 2026-10-21)\n",
|
||||
"## 2. 측점별 중심선 좌표 (02-001)\n",
|
||||
"| 측점(m) | 표기 | X | Y | 좌우 간격(m) | 인접 거리(m) | 측점차(m) | 잔차(m) |",
|
||||
"|---|---|---|---|---|---|---|---|"]
|
||||
prev = None
|
||||
for sta, x, y, g in a:
|
||||
label = f"{sta // 1000}+{sta % 1000:03d}"
|
||||
if prev is None:
|
||||
lines.append(f"| {sta} | {label} | {x:,.3f} | {y:,.3f} | {g:.2f} | — | — | — |")
|
||||
else:
|
||||
d = math.dist(prev[1:3], (x, y))
|
||||
lines.append(f"| {sta} | {label} | {x:,.3f} | {y:,.3f} | {g:.2f} | {d:.3f} | {sta - prev[0]} | {d - (sta - prev[0]):+.3f} |")
|
||||
prev = (sta, x, y)
|
||||
resid = [math.dist(a[i][1:3], a[i + 1][1:3]) - (a[i + 1][0] - a[i][0]) for i in range(len(a) - 1)]
|
||||
lines += ["",
|
||||
f"- 검증: 인접 측점 거리와 측점차의 잔차 최대 {max(abs(r) for r in resid):.3f} m (측점 9개, 8구간). 잔차는 보정하지 않고 그대로 기재",
|
||||
"- 범위: 4+700 ~ 5+500 (800m). **이 시트 밖의 연장은 확보된 도면이 없음**\n",
|
||||
"## 3. 02-002 시트 대조",
|
||||
f"- 02-002 추출 결과 {'02-001과 측점 좌표가 동일' if same else '02-001과 차이 있음'} ({'중복 시트 가능성' if same else '차이 확인 필요'}). 원본 파일 md5는 상이함 [출처: C0060302-002]",
|
||||
"- 02-002가 종단(프로파일) 시트인지 중복 시트인지는 사용자 확인 필요\n",
|
||||
"## 4. 미완 항목 (OPEN 등재 대상)",
|
||||
"- 종단 EL 추출 미실시 (측점별 계획고·지반고). `CR-TABL-TEX2`의 740.00~980.00 라벨은 20m 간격으로 보이나 측점 체계와 대응 미확인 [추론]",
|
||||
"- 허용오차 ±50mm 판정은 EL 추출 후 실시",
|
||||
"- 측점 4+700 이전·5+500 이후 구간 평면 도면 미확보\n"]
|
||||
open(OUT_MD, "w", encoding="utf-8").write("\n".join(lines) + "\n")
|
||||
print("written", OUT_MD, "same_as_002=", same, "max_resid=", round(max(abs(r) for r in resid), 3))
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the main-line drawing inventory report (DOC/02_분석) from the
|
||||
inventory JSON so that every number in the report comes from the extraction."""
|
||||
import collections
|
||||
import json
|
||||
import sys
|
||||
|
||||
SRC_JSON, OUT_MD = sys.argv[1], sys.argv[2]
|
||||
rows = json.load(open(SRC_JSON, encoding="utf-8"))
|
||||
ok = [r for r in rows if r["ok"]]
|
||||
err = [r for r in rows if not r["ok"]]
|
||||
units = collections.Counter(str(r.get("insunits")) for r in ok)
|
||||
n3d = sum(1 for r in ok if r["has_3d_geometry"])
|
||||
total_ents = sum(sum(r["entity_counts"].values()) for r in ok)
|
||||
|
||||
def top_types(r, k=3):
|
||||
return ", ".join(f"{t} {n}" for t, n in list(r["entity_counts"].items())[:k])
|
||||
|
||||
lines = []
|
||||
lines.append("# 대산2터널 본선 도면 인벤토리\n")
|
||||
lines.append("| 버전 | 일자 | 변경 내용 |\n|---|---|---|")
|
||||
lines.append("| v0.1.00 | 2026-10-21 | 최초 작성. 본선 21건(02 평면·종단 4, 03 본선굴착 8, 04 본선기타 9) DXF 인벤토리 |\n")
|
||||
lines.append("## 1. 개요\n")
|
||||
lines.append("- 입력: `RAG/03_터널명(대산2터널)/{02,03,04}` 하위 DXF 21건 (원본 DWG 변환본, 읽기 전용 사용)")
|
||||
lines.append(f"- 추출 결과: 성공 {len(ok)}건 / 실패 {len(err)}건 / 복구 모드 적용 {sum(1 for r in ok if r['recovered'])}건")
|
||||
lines.append(f"- 총 엔티티 {total_ents:,}개 (성공 파일 기준)")
|
||||
lines.append(f"- 3D 형상(Z 편차 있음) 보유 파일: **{n3d}건** — 전 도면이 2D이므로 3D 형상은 도면 해석으로 재구성해야 함 [출처: 본 인벤토리 `has_3d_geometry`]")
|
||||
lines.append(f"- `$INSUNITS` 분포: " + ", ".join(f"{k}={v}건" for k, v in units.items()) + " (0=미지정, 4=mm) — 단위는 도면 표기를 따름 (사용자 승인 2026-10-21)\n")
|
||||
lines.append("## 2. 파일별 추출 결과\n")
|
||||
lines.append("| # | 파일 | 상태 | 엔티티 | 주요 엔티티(상위 3) | 단위 | 코드페이지 |")
|
||||
lines.append("|---|---|---|---|---|---|---|")
|
||||
for i, r in enumerate(rows, 1):
|
||||
if r["ok"]:
|
||||
n = sum(r["entity_counts"].values())
|
||||
lines.append(f"| {i} | {r['file']} | OK | {n:,} | {top_types(r)} | {r['insunits']} | {r['codepage']} |")
|
||||
else:
|
||||
lines.append(f"| {i} | {r['file']} | **ERR** | — | {r.get('error','')} | — | — |")
|
||||
lines.append("")
|
||||
lines.append("## 3. 도면 주석 관찰 (정성, 판정 어휘: 있음 / 일부 있음 / 없음 / 확인 못함)\n")
|
||||
lines.append("| 항목 | 판정 | 근거 |\n|---|---|---|")
|
||||
lines.append("| 표준단면 편경사 기준 | 있음 | 「편경사 -2.0%를 기준으로 작성」 [출처: C0060303-001, 주석 1] |")
|
||||
lines.append("| 표준단면 방향 표기 범위 | 일부 있음 | 「당진방향 단면만 표기, 대산방향 단면은 생략」 [출처: C0060303-001, 주석 2] — 대산방향 형상은 이 도면으로 확정 불가 |")
|
||||
lines.append("| 숏크리트·라이닝 두께 수치 | 없음 | 「표준지보패턴도 참조」 [출처: C0060303-001, 주석 5] — 두께 값은 다른 도면(표준지보패턴) 필요 |")
|
||||
lines.append("| 굴착 순서 표기 | 있음 | N, N+1, N-1 단계 표기 및 1회 굴진장 [출처: C0060303-002] |")
|
||||
lines.append("| 굴진 1회당 길이 | 있음 | 「굴진 (1회당 0.8m)」 [출처: C0060303-008] |")
|
||||
lines.append("| 라이닝 구조도 적용 구간 | 일부 있음 | 「지보패턴 입출구부 A~C-2 구간의 구조적 취약구간에 적용」 [출처: C0060304-004, 주석 5] — 전 구간 적용 여부 확인 못함 |")
|
||||
lines.append("| 지보패턴 명칭 | 있음 | NP-4AD-1-D-2, NP-4BD-1-D-2, NP-5AE-1-WL-4, NP-5BE-1-WL-4, NP-6A, NP-6B [출처: 03-003~008 파일명] |")
|
||||
lines.append("| 평면·종단 지질·구배 표기 | 있음 | 구배 S=2.4913%, 지층명(붕적층·풍화토·풍화암·보통암) [출처: C0060302-001 텍스트] |")
|
||||
lines.append("| 구간 측점(STA) 정합 | 확인 못함 | 평면·종단·굴착 도면 간 측점 대응표 없음 — Task 3에서 대조 필요 [추론] |")
|
||||
lines.append("")
|
||||
lines.append("## 4. 확인·보류 항목 (OPEN 등재 대상)\n")
|
||||
lines.append("- **C0060304-003** (공동구 및 배수계획도 3): DXF 구조 오류(`Invalid sort handle code 331`), 복구 모드도 실패. 원본 DWG 재변환 필요. 본선 3D 골격에는 필수가 아니므로 배수 요소만 보류.")
|
||||
lines.append("- 대산방향 단면 형상 (C0060303-001 주석 2): 3D 대산방향 재구성 여부 결정 필요 [CLARIFICATION NEEDED].")
|
||||
lines.append("- 라이닝 두께 수치 출처 (표준지보패턴도 미보유): 두께 값 확정 전까지 `[미확정]`으로 표기.")
|
||||
lines.append("")
|
||||
lines.append("## 5. 다음 단계\n")
|
||||
lines.append("Task 3(평면·종단 선형 추출, HITL) 착수 전 위 4항의 확인 응답을 받는다. 본 인벤토리의 수치는 `Code/3d-drawing/data/inventory_mainline.json`에서 자동 생성되었다.")
|
||||
open(OUT_MD, "w", encoding="utf-8").write("\n".join(lines) + "\n")
|
||||
print("written", OUT_MD, "lines", len(lines))
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render the lining-reinforcement reference report (DOC/03_설계) from
|
||||
data/lining_spec.json written by extract_lining_spec.py. Values are read from
|
||||
the JSON, not typed. Usage: make_lining_doc.py <lining_spec.json> <out.md>"""
|
||||
import json
|
||||
import sys
|
||||
|
||||
SRC, OUT = sys.argv[1], sys.argv[2]
|
||||
d = json.load(open(SRC, encoding="utf-8"))
|
||||
S = d["sheets"]
|
||||
note5 = S[0]["note5"]
|
||||
conc = S[0]["concrete"][0].split("=")[-1]
|
||||
rebar = S[0]["rebar_grade"][0].split("=")[-1]
|
||||
agg = S[0]["aggregate"][0].split("Ø")[-1]
|
||||
r1_drawn = next(x for x in S[0]["radius_dims"] if x.startswith("R1="))
|
||||
r1_mm = float(r1_drawn.split("=")[1])
|
||||
lines = ["# 콘크리트 라이닝 구조도 판독 및 재료 사양 (초안)\n",
|
||||
"| 버전 | 일자 | 변경 내용 |", "|---|---|---|",
|
||||
"| v0.1.00 | 2026-10-21 | 최초 작성. 콘크리트라이닝구조도 C0060304-004~009 판독 (HITL 검토 대상) |\n",
|
||||
"## 1. 개요",
|
||||
"- 6장은 **지보패턴 입출구부 취약구간**의 철근 상세와 공통 사양이다. 도면에 측점(STA) 표기가 없어 어느 측점 구간에 적용되는지는 판독되지 않았다 ([미확정]).",
|
||||
"- 적용 범위(주기 5, 원문): " + note5 + "\n",
|
||||
"## 2. 시트별 패턴군",
|
||||
"| 도면 | 패턴군 | 상세 종류 | 철근 라벨(H13~H29 개수) |",
|
||||
"|---|---|---|---|"]
|
||||
for r in S:
|
||||
bars = ", ".join(f"{k} {v}" for k, v in r["rebar_sizes_label_count"].items())
|
||||
for i, g in enumerate(r["pattern_details"]):
|
||||
lab = bars if i == 0 else ""
|
||||
lines.append(f"| {r['file'][:14]} | {g['group']} | {g['kind']} | {lab} |")
|
||||
lines += ["",
|
||||
"- 철근 라벨 개수는 도면 시트 단위 합계다 (패턴군별 분리 불가). 첫 행에만 표기했다.\n",
|
||||
"## 3. 공통 재료 사양 (6장 동일)",
|
||||
f"- 콘크리트: {conc} ({S[0]['concrete'][0]})",
|
||||
f"- 철근: {rebar}, 도면 표기 SD400 ({S[0]['rebar_grade'][0]})",
|
||||
f"- 굵은골재 최대치수: Ø{agg} mm",
|
||||
"- 설계방법: 강도설계법, 피복두께 6 cm 이상 확보 (주기 1)",
|
||||
"- 이음장 600 mm (도면 치수), 겹침이음 1 cycle (도면 상세)",
|
||||
"- 라이닝 뒷면 주입재 강도 1.0 MPa 기대 (주기 11)",
|
||||
"- 천정부 방수쉬트 부착형 앵커, 철근처짐방지 앵커 적용 (주기 4, 12)\n",
|
||||
"## 4. 기준선과 치수",
|
||||
f"- 도면 표기 R1 = {r1_drawn} (6장 모두 동일 표기)",
|
||||
"- 표준단면 치수표(설계_표준단면치수표) R1 = 6,982 mm, 골격 모델은 6,982 mm 사용",
|
||||
f"- 라이닝 외면 R 7.282 m 에서 내면 R 6.982 m 를 빼면 두께 0.300 m, 표기 6.892 m 를 쓰면 0.390 m 이다. 표준단면 두께 300 mm 와 맞는 값은 6.982 m 이다.",
|
||||
"- R2 = 3,712 mm, R2′ = 3,910 mm (도면 표기, 표준단면 치수표와 일치)",
|
||||
"- EL. 0.000 = 노면 기준 (계획고 F = 0.000 과 일치 기록)",
|
||||
"- EL. -0.367 = 아치 중심 높이 (골격의 중심 c = -0.367 m 와 일치)\n",
|
||||
"## 5. 충돌·미확정",
|
||||
"- [확정 2026-10-21, 사용자 결정] R1 = 6.982 m 유지. 도면의 6.892 m 표기는 확인 대상 오기로 기록한다.",
|
||||
"- [미확정] 시트별 패턴군이 어느 측점 구간인지 (도면에 측점 없음)",
|
||||
"- [확인 필요] 라이닝 패턴군(A~C-2, D-1~2, E-1~2, WL-1~4, RP-1~2, P-2~3)과 굴착 순서도 패턴명(NP-4AD-1-D-2 등)의 대응. 사용자는 파일명에 기록되어 있다고 답했다. 예시 풀이(NP-5BE-1-WL-4 = NP-5B 의 D-1·D-2)는 파일명 속 WL-4 및 시트 패턴군(WL-3~4)과 맞지 않아 재확인이 필요하다.",
|
||||
"- 재료표 수량(총 중량)은 이번 판독에서 추출하지 않았다. 필요하면 재료표 추출을 별도 Task로 한다.",
|
||||
"- 철근 배치는 골격 LOD 범위 밖이라 모델에 넣지 않았다 (사용자 승인 3D 상세 수준: 축선·표준단면 스윕 골격).",
|
||||
"- 설계 조건과 상이하면 구조해석 검토 후 철근 보강 가능 (주기 5, 시공 중 판단 대상)\n",
|
||||
"## 6. 확인 요청 (HITL)",
|
||||
"1. NP-5BE-1-WL-4 의 D-1·D-2 풀이가 맞는지, 아니면 WL-4 가 어느 라이닝 패턴군인지",
|
||||
"2. 측점별 지보패턴 구간표가 확보되면 그 값으로 placeholder 구간을 교체할지",
|
||||
""]
|
||||
open(OUT, "w", encoding="utf-8").write("\n".join(lines))
|
||||
print("written", OUT, len(lines), "lines")
|
||||
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render the station-by-station lining pattern report (DOC/03_설계) from the
|
||||
CSVs written by extract_pattern_table.py. Values are read, not typed.
|
||||
Usage: make_pattern_doc.py <dangjin.csv> <daesan_ref.csv> <lining_spec.json> <out.md>"""
|
||||
import csv
|
||||
import json
|
||||
import sys
|
||||
|
||||
DJ, DS, LS, OUT = sys.argv[1:5]
|
||||
dj = list(csv.DictReader(open(DJ, encoding="utf-8")))
|
||||
ds = list(csv.DictReader(open(DS, encoding="utf-8")))
|
||||
lin = json.load(open(LS, encoding="utf-8"))["sheets"]
|
||||
drawing_groups = sorted({g["group"] for s in lin for g in s["pattern_details"] if g["group"]})
|
||||
dj_names = {r["lining_pattern"] for r in dj}
|
||||
ds_names = {r["lining_pattern"] for r in ds}
|
||||
# Drawing group labels (read from the sheet notes, 04-004..009) -> member patterns.
|
||||
# Explicit table: the labels mix letter ranges (A~C-2) and number ranges (WL-1~2).
|
||||
DRAWING_MEMBERS = {"A~C-2": ["A", "B-1", "B-2", "C-1", "C-2"], "D-1~2": ["D-1", "D-2"],
|
||||
"E-1~2": ["E-1", "E-2"], "WL-1~2": ["WL-1", "WL-2"], "WL-1~2,RP-1~2": ["WL-1", "WL-2", "RP-1", "RP-2"],
|
||||
"WL-3~4": ["WL-3", "WL-4"], "P-2~3": ["P-2", "P-3"], "RP-1~2": ["RP-1", "RP-2"]}
|
||||
members = set()
|
||||
for g in drawing_groups:
|
||||
members |= set(DRAWING_MEMBERS.get(g, [g]))
|
||||
missing_dj = sorted(m for m in members if m not in dj_names)
|
||||
only_ds = sorted(m for m in missing_dj if m in ds_names)
|
||||
none_any = sorted(m for m in missing_dj if m not in ds_names)
|
||||
L = ["# 측점별 콘크리트 라이닝 지보패턴표 (당진방향) (초안)\n",
|
||||
"| 버전 | 일자 | 변경 내용 |", "|---|---|---|",
|
||||
"| v0.1.00 | 2026-10-21 | 최초 작성. 엑셀 `참고_지보패턴현황` 당진방향 표를 측점별로 정리 (사용자 결정: 모델 placeholder 대체) |\n",
|
||||
"## 1. 출처와 범위",
|
||||
"- 원본: `RAG/03_터널명(대산2터널)/지보패턴현황_20260918.xlsx`, 시트 `참고_지보패턴현황`, 당진방향 표 (원본 행 55~96)",
|
||||
f"- 행 {len(dj)}개, 측점 {dj[0]['sta_from']} ~ {dj[-1]['sta_to']}, 빈틈 없음 (각 행 끝 = 다음 행 시작)",
|
||||
"- 모델 적용 범위 4+745 ~ 5+480 은 이 표로 전 구간 덮임 (735 m)",
|
||||
"- 지보패턴(M열) = 라이닝 패턴, 실시설계(O열) = 굴착 등급. 실시설계 칸이 빈 행은 위 행의 값을 이어 받음 (병합 셀 해석, [추론])\n",
|
||||
"## 2. 측점별 표",
|
||||
"| 측점 from | 측점 to | 연장(m) | 지보패턴 | 실시설계 | 굴착공법 | 비고 |",
|
||||
"|---|---|---|---|---|---|---|"]
|
||||
def sta(x):
|
||||
v = int(float(x)); return f"{v // 1000}+{v % 1000:03d}"
|
||||
for r in dj:
|
||||
exc = r["excavation"] + (" (이어받음)" if r["excavation_inherited"] == "True" and r["excavation"] else "")
|
||||
L.append(f"| {sta(r['sta_from'])} | {sta(r['sta_to'])} | {float(r['length_m']):g} | {r['lining_pattern']} | {exc} | {r['method'].replace(chr(10), ' ')} | {r['remark']} |")
|
||||
L += ["", "## 3. 도면과 표의 대응",
|
||||
f"- 라이닝 도면(04-004~009)의 패턴 {len(members)}개 중 당진 표에 있는 것: {len(members) - len(missing_dj)}개",
|
||||
f"- 당진 표에 없고 대산 표에만 있는 것: {', '.join(only_ds) if only_ds else '없음'} (대산 표 값 참고만, 적용 안 함 — 아래 4항)",
|
||||
f"- 두 표 모두 없는 것: {', '.join(none_any) if none_any else '없음'} → 측점 지정 불가, [미확정]\n",
|
||||
"## 4. 확인 필요 (HITL)",
|
||||
"- [확인 필요] 대산방향 표의 WL-4 (4+758.85~4+768.85, NP-5) 를 당진 측점에 적용할지. 두 방향이 별도 튜브라는 점은 [추론]이며 확인되지 않았다. 당진 표는 같은 측점에 WL-2/WL-1 을 보여 준다.",
|
||||
"- [확인 필요] 말씀하신 `NP-5BE-1-WL-4` 의 풀이 (NP-5B 의 E-1 ~ WL-4). 당진 NP-5 구간은 WL-2·WL-1 뿐이고, E-1 은 두 표 어디에도 없다.",
|
||||
"- [확인 필요] 엑셀 `개요2`·`1` 시트: 굴착 등급은 NP-4/5/6 (A·B 접미사 포함), 괄호 안은 지보 하위 패턴이라는 표기. 이 해석은 엑셀 표기와 일치하는 것으로 보이나 도면 명칭 대응은 확정하지 않았다.\n",
|
||||
"## 5. 적용 결과 (골격 모델)",
|
||||
"- placeholder 2 m 구간은 폐기하고 이 표로 대체 (사용자 결정 2026-10-21)",
|
||||
"- 뷰어의 라이닝 클릭 값: 측점, 지보패턴(본 표), 라이닝 사양 (C0060304-004~009)",
|
||||
""]
|
||||
open(OUT, "w", encoding="utf-8").write("\n".join(L))
|
||||
print("written", OUT, "rows", len(dj), "drawing groups", len(members), "missing in dangjin", missing_dj)
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render the confirmed station table (지반고 = A열, 계획고 = B열, 20 m station
|
||||
numbering per user decision 2026-10-21) into DOC/03_설계 and check the design
|
||||
grade against the drawn label S=2.4913% (정상 구배 검산). No value is adjusted."""
|
||||
import csv
|
||||
import sys
|
||||
|
||||
SRC, OUT = sys.argv[1], sys.argv[2]
|
||||
rows = []
|
||||
with open(SRC, encoding="utf-8") as fh:
|
||||
rd = list(csv.DictReader(fh))
|
||||
gl, dg = {}, {}
|
||||
for r in rd:
|
||||
sta = int(r["station_anchor"])
|
||||
if float(r["across_m"]) < -25:
|
||||
gl[sta] = float(r["value"])
|
||||
else:
|
||||
dg[sta] = float(r["value"])
|
||||
stas = sorted(set(gl) & set(dg))
|
||||
n = len(stas)
|
||||
xs = [s for s in stas]
|
||||
mx = sum(xs) / n
|
||||
my = sum(dg[s] for s in stas) / n
|
||||
slope = sum((s - mx) * (dg[s] - my) for s in stas) / sum((s - mx) ** 2 for s in stas)
|
||||
grade_pct = slope * 100
|
||||
resid = [dg[s] - (my + slope * (s - mx)) for s in stas]
|
||||
max_res = max(abs(r) for r in resid)
|
||||
lines = ["# 본선 종단 측점값 — 지반고·계획고 (초안, 확정 해석 반영)\n",
|
||||
"| 버전 | 일자 | 변경 내용 |", "|---|---|---|",
|
||||
"| v0.1.00 | 2026-10-21 | 최초 작성. 02-002 종단 눈금 4+740~5+480, 지반고(A열)·계획고(B열) 확정 해석 반영 |\n",
|
||||
"## 1. 확정 사항 (사용자 답변 2026-10-21)",
|
||||
"- 소수 A열(중심선 측 약 -33m) = **지반고**, B열(약 -16m) = **계획고** [출처: C0060302-002, CR-TABL-TEX2]",
|
||||
"- 측점은 **측점 번호 20m 기준**. 도면상 눈금 거리 21.27m는 사용하지 않음 (축척 1.0635배 불일치 기록)",
|
||||
"- 공백 구간(4+700~4+740, 5+480~5+500)은 **[미확정]**, 확보 구간만 모델링\n",
|
||||
"## 2. 측점별 지반고·계획고",
|
||||
"| 측점(m) | 표기 | 지반고 GL(m) | 계획고(m) |", "|---|---|---|---|"]
|
||||
for s in stas:
|
||||
lines.append(f"| {s} | {s // 1000}+{s % 1000:03d} | {gl[s]:.3f} | {dg[s]:.3f} |")
|
||||
lines += ["",
|
||||
"## 3. 검산",
|
||||
f"- 전구간 직선 회귀 구배 {grade_pct:.4f}%는 **단일 구배가 아님**을 뜻하므로 채택하지 않음 (잔차 최대 {max_res:.3f} m)",
|
||||
"- 구간별 구배 (계획고 2점 기울기, 도면 표기 S=2.4913%와 비교):"]
|
||||
for a, b in [(4740, 4800), (4800, 4900), (4900, 5000), (5000, 5100), (5100, 5200), (5200, 5300), (5300, 5400), (5400, 5480)]:
|
||||
seg = [k for k in stas if a <= k <= b]
|
||||
if len(seg) >= 2:
|
||||
g = (dg[seg[-1]] - dg[seg[0]]) / (seg[-1] - seg[0]) * 100
|
||||
lines.append(f" - {a // 1000}+{a % 1000:03d} ~ {b // 1000}+{b % 1000:03d}: {g:.4f}%")
|
||||
lines += ["- 해석: 4+740~5+300은 2.491%로 S=2.4913%와 일치. 5+300 이후 구배 감소는 종곡선 구간으로 보이나 [추론], 종곡선 제원은 도면에서 확인 못함 [확인 못함]",
|
||||
"- 지반고는 구배 검산 대상이 아님 (지형 값)\n",
|
||||
"## 4. 미확정·후속 (OPEN 등재 대상)",
|
||||
"- 4+700~4+740, 5+480~5+500 종단 값 없음 → [미확정]",
|
||||
"- 허용오차 ±50mm 판정: 본 초안은 도면 표기 값 그대로이며, 3D 대조 판정은 Task 7 이후 수행",
|
||||
"- 라이닝 두께 수치: 04-004~009 콘크리트라이닝 구조도 확인 예정 (사용자 지시 2026-10-21)\n"]
|
||||
open(OUT, "w", encoding="utf-8").write("\n".join(lines) + "\n")
|
||||
print(f"stations={n} grade={grade_pct:.4f}% max_resid={max_res:.3f}")
|
||||
@@ -0,0 +1,42 @@
|
||||
import sys, time, pickle
|
||||
sys.path += ['/root/.local/lib/python3.13/site-packages','/usr/local/lib/python3.13/dist-packages','/usr/lib/python3/dist-packages']
|
||||
from build123d import import_step, Face
|
||||
import numpy as np
|
||||
shape = import_step("/mnt/user-data/uploads/97.과제/Code/3d-drawing/model/tunnel_solids.step")
|
||||
solids = shape.solids()
|
||||
pickle_out = {}
|
||||
import json
|
||||
meta=json.load(open('/mnt/user-data/uploads/97.과제/Code/3d-drawing/model/tunnel_solids_meta.json'))
|
||||
comps=[c for c in meta['components'] if c['group']!='시설한계(참조선)']
|
||||
print("non-clearance comps", len(comps))
|
||||
# match by volume greedily in meta order
|
||||
used=set(); mapping={}
|
||||
for c in comps:
|
||||
best=None;bd=1e9
|
||||
for j,s in enumerate(solids):
|
||||
if j in used: continue
|
||||
d=abs(s.volume-c['volume_m3'])
|
||||
if d<bd: bd=d;best=j
|
||||
if bd<0.02:
|
||||
used.add(best); mapping[c['id']]=best
|
||||
else:
|
||||
mapping[c['id']]=None
|
||||
print("matched", sum(v is not None for v in mapping.values()), "of", len(comps), "unused solids", len(solids)-len(used))
|
||||
print("first 10 mapping", list(mapping.items())[:10])
|
||||
pickle.dump({'mapping':mapping}, open('/tmp/verify_daesan2_run/mapping.pkl','wb'))
|
||||
# faces of solid 0 (LIN_01)
|
||||
for idx in [mapping['LIN_01_P-3']]:
|
||||
s=solids[idx]
|
||||
fs=s.faces()
|
||||
print("solid",idx,"nfaces",len(fs), "vol", round(s.volume,3))
|
||||
rows=[]
|
||||
for f in fs:
|
||||
gt=f.geom_type
|
||||
c=f.center(); a=f.area
|
||||
try:
|
||||
n=f.normal_at(c)
|
||||
except Exception as ex:
|
||||
n=None
|
||||
rows.append((gt, round(a,1), [round(x,3) for x in (c.X,c.Y,c.Z)], None if n is None else [round(x,4) for x in (n.X,n.Y,n.Z)]))
|
||||
rows.sort(key=lambda r: -r[1])
|
||||
for r in rows[:25]: print(r)
|
||||
@@ -0,0 +1,69 @@
|
||||
import sys
|
||||
exec(open('/tmp/verify_daesan2_run/scripts/hd2.py').read().split('allres={}')[0])
|
||||
from scipy.spatial import cKDTree
|
||||
import numpy as np
|
||||
extra={
|
||||
'PAV_sideL': seg(9526.3,15764.0,9526.3,15484.0), 'PAV_sideR': seg(18726.3,15580.0,18726.3,15300.0),
|
||||
'DRN_topL': seg(9026.3,15694.0,9476.3,15703.0), 'DRN_topR': seg(18776.3,15519.0,19226.3,15510.0),
|
||||
}
|
||||
REF.update(extra)
|
||||
def dd(A,B):
|
||||
d1,_=cKDTree(A).query(B); d2,_=cKDTree(B).query(A); return d1,d2
|
||||
for st in ['4745.0005','5100.0','5469.9995']:
|
||||
print("=== station", st, "sigma=+1 slope(dxf)", round(FIT[st]['slope_top'],5))
|
||||
L=to_dxf(pts(st,lambda g,i:g=='라이닝'),st,1); E=to_dxf(pts(st,lambda g,i:g=='굴착면'),st,1)
|
||||
P=to_dxf(pts(st,lambda g,i:g=='노면(계획고)'),st,1); B=to_dxf(pts(st,lambda g,i:g=='기층·인버트(가정)'),st,1)
|
||||
D=to_dxf(pts(st,lambda g,i:g=='배수(측구)'),st,1)
|
||||
# LIN full arcs (no clipping) and clipped to model extent
|
||||
arcs=['LIN_crown_outer','LIN_crown_inner','LIN_haunchL_outer','LIN_haunchL_inner','LIN_haunchR_outer','LIN_haunchR_inner']
|
||||
for k in arcs:
|
||||
d,_=cKDTree(L).query(REF[k]); print(f" LIN {k:18s} max={d.max():7.1f} mm (at y_min_ref={REF[k][:,1].min():.0f})")
|
||||
ymin_L=L[L[:,0]<C0[0]][:,1].min(); ymin_R=L[L[:,0]>C0[0]][:,1].min()
|
||||
lr=np.vstack([REF[k] for k in arcs]); d,_=cKDTree(L).query(lr)
|
||||
inside=((lr[:,0]<C0[0])&(lr[:,1]>=ymin_L))|((lr[:,0]>=C0[0])&(lr[:,1]>=ymin_R))
|
||||
print(f" LIN within model extent (y>={ymin_L:.0f}/{ymin_R:.0f}) max={d[inside].max():.1f} mm; drawn part below model extent (not modelled) n={int((~inside).sum())} pts, y range {lr[~inside,1].min():.0f}-{lr[~inside,1].max():.0f}")
|
||||
fl=np.vstack([REF['LIN_footL'],REF['LIN_footR']]); a,b=dd(L,fl); print(f" LIN foot lines (CS-CONC-MAJR wall/foot) ref->model max={a.max():.1f} model->ref(near band) ")
|
||||
# EXC
|
||||
ea=['EXC_crown','EXC_haunchL','EXC_haunchR']
|
||||
eymL=E[E[:,0]<C0[0]][:,1].min(); eymR=E[E[:,0]>C0[0]][:,1].min()
|
||||
er=np.vstack([REF[k] for k in ea]); d,_=cKDTree(E).query(er)
|
||||
ins=((er[:,0]<C0[0])&(er[:,1]>=eymL))|((er[:,0]>=C0[0])&(er[:,1]>=eymR))
|
||||
print(f" EXC arcs within model extent (y>={eymL:.0f}/{eymR:.0f}) max={d[ins].max():.1f} mm; crown max={cKDTree(E).query(REF['EXC_crown'])[0].max():.1f}; below model extent n={int((~ins).sum())} y {er[~ins,1].min():.0f}-{er[~ins,1].max():.0f}")
|
||||
# PAV model vs drawn top/bottom/sides
|
||||
pref=np.vstack([REF['PAV_top'],REF['PAV_bot'],REF['PAV_sideL'],REF['PAV_sideR']])
|
||||
Pm=P[(P[:,0]>9526.3-150)&(P[:,0]<18726.3+150)]
|
||||
a,b=dd(Pm,pref); print(f" PAV ref->model max={a.max():.1f} mm ; model->ref max={b.max():.1f} mm (top/bottom/sides)")
|
||||
# BASE: top envelope vs base top line; bottom envelope vs base bottom line, within road span
|
||||
bw=B[(B[:,0]>=9526.3)&(B[:,0]<=18726.3)]
|
||||
xs=np.linspace(9526.3,18726.3,921)
|
||||
top_line=15484.0+(xs-9526.3)*(15300-15484)/9200.0; bot_line=15334.0+(xs-9526.3)*(15150-15334)/9200.0
|
||||
# model envelope per x-bin (1 m bins for robust): max y (top) and min y (bottom)
|
||||
tops=[];bots=[]
|
||||
for x0 in np.arange(9526.3,18726.3,200):
|
||||
m=(bw[:,0]>=x0)&(bw[:,0]<x0+200)
|
||||
if m.sum()==0: continue
|
||||
xm=bw[m,0].mean(); tops.append((xm,bw[m,1].max())); bots.append((xm,bw[m,1].min()))
|
||||
tops=np.array(tops); bots=np.array(bots)
|
||||
et=np.abs(tops[:,1]-np.interp(tops[:,0],xs,top_line)); eb=np.abs(bots[:,1]-np.interp(bots[:,0],xs,bot_line))
|
||||
print(f" BASE top envelope vs drawn top (15484->15300): max={et.max():.1f} mm, mean={et.mean():.1f}; bottom envelope vs drawn bottom (15334->15150): max={eb.max():.1f} mm, mean={eb.mean():.1f}")
|
||||
print(f" BASE model bottom envelope at span ends: x={bots[0,0]:.0f} y={bots[0,1]:.1f} / x={bots[-1,0]:.0f} y={bots[-1,1]:.1f}; top envelope ends y={tops[0,1]:.1f}/{tops[-1,1]:.1f}")
|
||||
print(f" BASE model extent outside road span: x {B[:,0].min():.0f}..{B[:,0].max():.0f}, y {B[:,1].min():.0f}..{B[:,1].max():.0f}")
|
||||
# DRN
|
||||
dref=np.vstack([REF[k] for k in ['DRN_wallL1','DRN_wallL2','DRN_botL','DRN_pipeL','DRN_wallR1','DRN_wallR2','DRN_botR','DRN_pipeR','DRN_topL','DRN_topR']])
|
||||
dm=D[(D[:,0]>8900)&(D[:,0]<19300)]
|
||||
a,b=dd(dm,dref); print(f" DRN ref->model max={a.max():.1f} mm ; model->ref max={b.max():.1f} mm (walls, bottoms, pipes, tops)")
|
||||
# vertical datum check at tunnel centre and road centre
|
||||
fit=FIT[st]; uc,vc,_=fit['outer']
|
||||
top_c=fit['slope_top']*(0-uc)+0 ;
|
||||
# top envelope at u for DXF tunnel centre (u=uc) and road centre (x=9776.3 -> u=uc-4.35)
|
||||
uu=P[:,0];
|
||||
def top_env(xdxf):
|
||||
um=uc+(xdxf-C0[0])/1000.0
|
||||
m=np.abs(pts(st,lambda g,i:g=='노면(계획고)')[:,0]-um)<0.05
|
||||
return pts(st,lambda g,i:g=='노면(계획고)')[m,1].max()
|
||||
Pm_raw=pts(st,lambda g,i:g=='노면(계획고)')
|
||||
for lab,xdxf,ydxf in [('tunnel centre x=14126.3',14126.3,15672.0),('road centre x=9776.3',9776.3,15759.0)]:
|
||||
um=uc+(xdxf-C0[0])/1000.0
|
||||
m=np.abs(Pm_raw[:,0]-um)<0.05
|
||||
yt=(Pm_raw[m,1].max()-vc)*1000; yb=(Pm_raw[m,1].min()-vc)*1000
|
||||
print(f" datum {lab}: model top={yt:.1f} mm, bottom={yb:.1f} mm above lining centre | DXF top at this x = {ydxf-15305:.1f} mm above lining centre")
|
||||
@@ -0,0 +1,23 @@
|
||||
import sys
|
||||
exec(open('/tmp/verify_daesan2_run/scripts/final.py').read().split("for st in ['4745.0005','5100.0','5469.9995']:")[0])
|
||||
for st in ['4745.0005','5100.0']:
|
||||
B=to_dxf(pts(st,lambda g,i:g=='기층·인버트(가정)'),st,1); D=to_dxf(pts(st,lambda g,i:g=='배수(측구)'),st,1)
|
||||
bw=B[(B[:,0]>=9526.3+150)&(B[:,0]<=18726.3-150)]
|
||||
xs=np.arange(9676.3,18576.3,50.0)
|
||||
top_line=lambda x: 15484.0+(x-9526.3)*(15300-15484)/9200.0
|
||||
bot_line=lambda x: 15334.0+(x-9526.3)*(15150-15334)/9200.0
|
||||
tt=[];bb=[]
|
||||
for x0 in xs:
|
||||
m=(bw[:,0]>=x0-25)&(bw[:,0]<x0+25)
|
||||
if m.sum()==0: continue
|
||||
tt.append(bw[m,1].max()-top_line(x0)); bb.append(bw[m,1].min()-bot_line(x0))
|
||||
tt=np.array(tt); bb=np.array(bb)
|
||||
print(f"{st}: BASE top (pavement-bottom interface), interior 150mm from ends: model-drawn max|dev|={np.abs(tt).max():.1f} mm, mean={tt.mean():.1f} mm (signed mean)")
|
||||
print(f"{st}: BASE bottom (drawn excavation floor 15334->15150), interior: max|dev|={np.abs(bb).max():.1f} mm, mean={bb.mean():.1f} mm (signed; negative=model below drawing)")
|
||||
# drains body vs lid
|
||||
body=np.vstack([REF[k] for k in ['DRN_wallL1','DRN_wallL2','DRN_botL','DRN_pipeL','DRN_wallR1','DRN_wallR2','DRN_botR','DRN_pipeR']])
|
||||
lid=np.vstack([REF['DRN_topL'],REF['DRN_topR']])
|
||||
dm=D[(D[:,0]>8900)&(D[:,0]<19300)]
|
||||
a,_=cKDTree(dm).query(body); b,_=cKDTree(dm).query(lid)
|
||||
print(f"{st}: DRN body (walls/bottom/pipe) ref->model max={a.max():.1f} mm; lid (450-wide top line) ref->model max={b.max():.1f} mm")
|
||||
print(f"{st}: DRN model x-extent per ditch: left {dm[dm[:,0]<14126,0].min():.0f}-{dm[dm[:,0]<14126,0].max():.0f}, right {dm[dm[:,0]>14126,0].min():.0f}-{dm[dm[:,0]>14126,0].max():.0f}")
|
||||
@@ -0,0 +1,102 @@
|
||||
import sys, json, pickle, collections
|
||||
sys.path += ['/root/.local/lib/python3.13/site-packages','/usr/local/lib/python3.13/dist-packages','/usr/lib/python3/dist-packages']
|
||||
import numpy as np
|
||||
from build123d import import_step, Shape
|
||||
from OCP.BRepAlgoAPI import BRepAlgoAPI_Section
|
||||
from OCP.gp import gp_Pln, gp_Pnt, gp_Dir
|
||||
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeFace
|
||||
from OCP.TopExp import TopExp_Explorer
|
||||
from OCP.TopAbs import TopAbs_EDGE
|
||||
solids = import_step("/mnt/user-data/uploads/97.과제/Code/3d-drawing/model/tunnel_solids.step").solids()
|
||||
mp = pickle.load(open('/tmp/verify_daesan2_run/mapping.pkl','rb'))['mapping']
|
||||
caps = json.load(open('/tmp/verify_daesan2_run/caps.json'))
|
||||
ch = json.load(open('/tmp/verify_daesan2_run/chain.json'))
|
||||
meta = json.load(open('/mnt/user-data/uploads/97.과제/Code/3d-drawing/model/tunnel_solids_meta.json'))
|
||||
comps = {c['id']: c for c in meta['components'] if c['group']!='시설한계(참조선)'}
|
||||
solid_comp = {v:k for k,v in mp.items()}
|
||||
# 1) boundary points from lining chain
|
||||
pts = []
|
||||
for k in ch['lin']:
|
||||
pts.append(np.array(ch['starts'][k])); pts.append(np.array(ch['ends'][k]))
|
||||
P = np.array(pts)[:, :2]
|
||||
uniq = []
|
||||
for p in P:
|
||||
if not any(np.linalg.norm(p-q) < 1e-3 for q in uniq): uniq.append(p)
|
||||
uniq = np.array(uniq)
|
||||
print("unique boundary points", len(uniq), "(expect 41)")
|
||||
D = P[-1]-P[0] if False else (np.array(ch['ends'][ch['lin'][-1]])[:2]-np.array(ch['starts'][ch['lin'][0]])[:2])
|
||||
proj = uniq@D/np.linalg.norm(D)
|
||||
order = np.argsort(proj); B = uniq[order]
|
||||
cum = np.concatenate([[0], np.cumsum(np.linalg.norm(np.diff(B,axis=0),axis=1))])
|
||||
Sb = 4745 + cum
|
||||
M = sorted(set([c['from_sta'] for c in comps.values()] + [5480]))
|
||||
print("boundary count", len(Sb), "meta boundary count", len(M))
|
||||
print("max |station_chain - meta_boundary| (m):", round(float(np.max(np.abs(Sb-np.array(M)))),3) if len(Sb)==len(M) else 'count mismatch')
|
||||
# 2) station for each solid by centroid projection
|
||||
def station_of(p):
|
||||
best=None
|
||||
for k in range(len(B)-1):
|
||||
a,b=B[k],B[k+1]; t=b-a; L2=t@t
|
||||
f=np.clip(((p-a)@t)/L2,0,1); q=a+f*t; d=np.linalg.norm(p-q)
|
||||
if best is None or d<best[0]: best=(d, Sb[k]+f*(Sb[k+1]-Sb[k]), k, f)
|
||||
return best
|
||||
sol_info={}
|
||||
for i,s in enumerate(solids):
|
||||
c=s.center(); d,st,k,f=station_of(np.array([c.X,c.Y]))
|
||||
cid=solid_comp[i]; sol_info[i]=dict(cid=cid, group=comps[cid]['group'], st=st, lat=d)
|
||||
# 3) pair within group by sorted mid
|
||||
assign={}
|
||||
groups=collections.defaultdict(list)
|
||||
for i,v in sol_info.items(): groups[v['group']].append(i)
|
||||
maxres=0
|
||||
for g,idxs in groups.items():
|
||||
cs=[c for c in comps.values() if c['group']==g]
|
||||
idxs_s=sorted(idxs, key=lambda i: sol_info[i]['st'])
|
||||
cs_s=sorted(cs, key=lambda c: (c['from_sta']+c['to_sta'])/2)
|
||||
if len(idxs_s)!=len(cs_s): print("COUNT MISMATCH", g, len(idxs_s), len(cs_s))
|
||||
for i,c in zip(idxs_s, cs_s):
|
||||
mid=(c['from_sta']+c['to_sta'])/2
|
||||
res=abs(sol_info[i]['st']-mid); maxres=max(maxres,res)
|
||||
assign[i]=dict(id=c['id'], group=g, frm=c['from_sta'], to=c['to_sta'], mid_chain=sol_info[i]['st'], lat=sol_info[i]['lat'])
|
||||
print("max residual solid-centre station vs comp mid (m):", round(maxres,3))
|
||||
# lateral offset sanity
|
||||
print("max lateral offset of solid centroid from chain (m):", round(max(v['lat'] for v in assign.values()),3))
|
||||
# 4) slice
|
||||
def slice_solid(idx, Q, T):
|
||||
nrm=np.array([T[0],T[1],0.0])
|
||||
pl=gp_Pln(gp_Pnt(float(Q[0]),float(Q[1]),0.0), gp_Dir(float(nrm[0]),float(nrm[1]),0.0))
|
||||
face=BRepBuilderAPI_MakeFace(pl,-80,80,-80,80).Face()
|
||||
sec=BRepAlgoAPI_Section(solids[idx].wrapped, face); sec.Build()
|
||||
polys=[]
|
||||
exp=TopExp_Explorer(sec.Shape(), TopAbs_EDGE)
|
||||
while exp.More():
|
||||
e=Shape.cast(exp.Current()); L=e.length; N=max(2,int(L/0.02))
|
||||
polys.append([[p.X,p.Y,p.Z] for p in (e.position_at(j/N) for j in range(N+1))])
|
||||
exp.Next()
|
||||
return polys
|
||||
def plane_at(s):
|
||||
k=int(np.searchsorted(Sb, s, side='right')-1); k=min(max(k,0),len(B)-2)
|
||||
f=(s-Sb[k])/(Sb[k+1]-Sb[k])
|
||||
Q=(1-f)*B[k]+f*B[k+1]; T=(B[k+1]-B[k]); T=T/np.linalg.norm(T)
|
||||
return Q, T
|
||||
out={}
|
||||
for s in [4745.0005, 5100.0, 5469.9995]:
|
||||
Q,T=plane_at(s); eh=np.array([-T[1],T[0]])
|
||||
print(f"== station {s}: Q=({Q[0]:.3f},{Q[1]:.3f}) T=({T[0]:.5f},{T[1]:.5f})")
|
||||
out[str(s)]={'Q':Q.tolist(),'T':T.tolist(),'eh':eh.tolist(),'solids':{}}
|
||||
cnt=collections.Counter()
|
||||
for i,a in assign.items():
|
||||
if not (a['frm'] <= s <= a['to']): continue
|
||||
polys=slice_solid(i,Q,T)
|
||||
uv=[]
|
||||
for pl_ in polys:
|
||||
arr=np.array(pl_); u=(arr[:,:2]-Q)@eh; uv.append(np.stack([u,arr[:,2]],1).tolist())
|
||||
out[str(s)]['solids'][str(i)]={'id':a['id'],'group':a['group'],'polys':uv}
|
||||
cnt[a['group']]+=1
|
||||
allp=np.vstack([np.array(x) for x in uv]) if uv else np.zeros((0,2))
|
||||
if len(allp): print(f" {a['group']:<12} {a['id']:<16} nloops={len(uv)} u[{allp[:,0].min():.3f},{allp[:,0].max():.3f}] v[{allp[:,1].min():.3f},{allp[:,1].max():.3f}]")
|
||||
else: print(" EMPTY", a['group'], a['id'])
|
||||
print(" counts", dict(cnt))
|
||||
json.dump(out, open('/tmp/verify_daesan2_run/sections_final.json','w'))
|
||||
json.dump({str(i):a for i,a in assign.items()}, open('/tmp/verify_daesan2_run/assign.json','w'), ensure_ascii=False)
|
||||
print("saved")
|
||||
@@ -0,0 +1,110 @@
|
||||
import sys, json, math
|
||||
sys.path += ['/root/.local/lib/python3.13/site-packages','/usr/local/lib/python3.13/dist-packages','/usr/lib/python3/dist-packages']
|
||||
import numpy as np
|
||||
from scipy.spatial import cKDTree
|
||||
SEC=json.load(open('/tmp/verify_daesan2_run/sections_final.json'))
|
||||
FIT=json.load(open('/tmp/verify_daesan2_run/fits.json'))
|
||||
C0=(14126.3,15305.0)
|
||||
def pts(st, pred):
|
||||
out=[]
|
||||
for sid,v in SEC[st]['solids'].items():
|
||||
if pred(v['group'], v['id']):
|
||||
for poly in v['polys']: out.append(np.array(poly)[:,:2])
|
||||
return np.vstack(out)
|
||||
def arc(cx,cy,r,a0,a1,step=1.0):
|
||||
if a1<a0: a1+=360
|
||||
n=max(2,int(r*math.radians(a1-a0)/step)); th=np.radians(np.linspace(a0,a1,n))
|
||||
return np.c_[cx+r*np.cos(th), cy+r*np.sin(th)]
|
||||
def seg(x0,y0,x1,y1,step=1.0):
|
||||
n=max(2,int(math.hypot(x1-x0,y1-y0)/step)); t=np.linspace(0,1,n); return np.c_[x0+(x1-x0)*t,y0+(y1-y0)*t]
|
||||
def circ(cx,cy,r,step=1.0):
|
||||
n=max(8,int(2*math.pi*r/step)); th=np.linspace(0,2*math.pi,n,endpoint=False); return np.c_[cx+r*np.cos(th),cy+r*np.sin(th)]
|
||||
REF={
|
||||
'LIN_crown_outer': arc(*C0,7282,40,140), 'LIN_crown_inner': arc(*C0,6982,40,140),
|
||||
'LIN_haunchL_outer': arc(11959.9,17122.8,4454,140,204.67), 'LIN_haunchL_inner': arc(11621.3,17406.9,3712,140,195.56),
|
||||
'LIN_haunchR_outer': arc(16105.0,16965.3,4699,336.35,40), 'LIN_haunchR_inner': arc(16479.6,17279.7,3910,344.38,40),
|
||||
'LIN_footL': np.vstack([seg(8171.3,16410.9,8471.3,16410.9), seg(8471.3,16410.9,8621.3,16410.9), seg(8621.3,16410.9,8651.3,16000.9), seg(8651.3,16000.9,8771.3,15825.9), seg(8771.3,15825.9,8771.3,15750.9)]),
|
||||
'LIN_footR': np.vstack([seg(20181.3,16226.9,19781.3,16226.9), seg(19781.3,16226.9,19631.3,16226.9), seg(19631.3,16226.9,19601.3,15816.9), seg(19601.3,15816.9,19481.3,15641.9), seg(19481.3,15641.9,19481.3,15566.9)]),
|
||||
'EXC_crown': arc(*C0,7402,40,140), 'EXC_haunchL': arc(11959.9,17122.8,4574,140,203.98), 'EXC_haunchR': arc(16105.0,16965.3,4819,336.97,40),
|
||||
'PAV_top': seg(9526.3,15764.0,18726.3,15580.0), 'PAV_bot': seg(9526.3,15484.0,18726.3,15300.0), 'PAV_sideL': seg(9526.3,15764.0,9526.3,15484.0), 'PAV_sideR': seg(18726.3,15580.0,18726.3,15300.0),
|
||||
'BASE_top': seg(9526.3,15484.0,18726.3,15300.0), 'BASE_bot': seg(9526.3,15334.0,18726.3,15150.0),
|
||||
'DRN_wallL1': seg(9101.3,14864.0,9101.3,15695.5), 'DRN_wallL2': seg(9401.3,14864.0,9401.3,15701.5), 'DRN_botL': seg(9101.3,14864.0,9401.3,14864.0), 'DRN_pipeL': circ(9251.3,15064.0,150),
|
||||
'DRN_wallR1': seg(18851.3,14680.0,18851.3,15517.5), 'DRN_wallR2': seg(19151.3,14680.0,19151.3,15511.5), 'DRN_botR': seg(18851.3,14680.0,19151.3,14680.0), 'DRN_pipeR': circ(19001.3,14880.0,150),
|
||||
}
|
||||
def to_dxf(P, st, sig):
|
||||
uc,vc,_=FIT[st]['outer']
|
||||
return np.c_[C0[0]+sig*(P[:,0]-uc)*1000.0, C0[1]+(P[:,1]-vc)*1000.0]
|
||||
def dist_stats(M, R, label):
|
||||
"""M model pts (dxf frame), R reference pts. Returns d(ref->model) max, d(model->ref) max within band."""
|
||||
if len(M)==0 or len(R)==0: return None
|
||||
tM=cKDTree(M); tR=cKDTree(R)
|
||||
dR,_=tM.query(R); dM,_=tR.query(M)
|
||||
return dict(ref_to_model_max=float(dR.max()), ref_to_model_mean=float(dR.mean()), model_to_ref_max=float(dM.max()), model_to_ref_mean=float(dM.mean()), n_ref=len(R), n_model=len(M))
|
||||
def run(sig, st, feats):
|
||||
res={}
|
||||
L=to_dxf(pts(st,lambda g,i:g=='라이닝'),st,sig); E=to_dxf(pts(st,lambda g,i:g=='굴착면'),st,sig)
|
||||
P=to_dxf(pts(st,lambda g,i:g=='노면(계획고)'),st,sig); B=to_dxf(pts(st,lambda g,i:g=='기층·인버트(가정)'),st,sig)
|
||||
D=to_dxf(pts(st,lambda g,i:g=='배수(측구)'),st,sig)
|
||||
def side_clip(R, M, side):
|
||||
# keep reference points on given side (L: x<C0x, R: x>C0x) and above model's lowest y on that side minus 50mm
|
||||
sm = M[(M[:,0]<C0[0])] if side=='L' else M[(M[:,0]>C0[0])]
|
||||
sr = R[(R[:,0]<C0[0])] if side=='L' else R[(R[:,0]>C0[0])]
|
||||
if len(sm)==0: return sr, 0, None
|
||||
ymin=sm[:,1].min()-0.05*1000
|
||||
keep=sr[sr[:,1]>=ymin]
|
||||
dropped=len(sr)-len(keep)
|
||||
rng=None if dropped==0 else [round(float(sr[sr[:,1]<ymin,1].min())),round(float(sr[sr[:,1]<ymin,1].max()))]
|
||||
return keep, dropped, rng
|
||||
out={}
|
||||
# Lining: arcs and foot lines; clip below model extent per side
|
||||
lin_ref=np.vstack([REF[k] for k in ['LIN_crown_outer','LIN_crown_inner','LIN_haunchL_outer','LIN_haunchL_inner','LIN_haunchR_outer','LIN_haunchR_inner']])
|
||||
keepL,dL,rL=side_clip(lin_ref[lin_ref[:,0]<C0[0]],L,'L'); keepR,dR_,rR=side_clip(lin_ref[lin_ref[:,0]>C0[0]],L,'R')
|
||||
crown=REF['LIN_crown_outer'];
|
||||
out['LIN_arcs']=dist_stats(L, np.vstack([crown, REF['LIN_crown_inner'], keepL, keepR]), 'LIN')
|
||||
out['LIN_arcs']['clipped_below_model_extent']={'L':dL,'R':dR_,'yrange_L':rL,'yrange_R':rR}
|
||||
out['LIN_footlines']=dist_stats(L, np.vstack([REF['LIN_footL'],REF['LIN_footR']]), 'LIN_foot')
|
||||
# per-arc max deviations (ref->model), by arc name
|
||||
perarc={}
|
||||
for k in ['LIN_crown_outer','LIN_crown_inner','LIN_haunchL_outer','LIN_haunchL_inner','LIN_haunchR_outer','LIN_haunchR_inner']:
|
||||
R=REF[k]; tM=cKDTree(L); d,_=tM.query(R); perarc[k]=round(float(d.max())*1,1)
|
||||
out['LIN_perarc_ref2model_max_mm']=perarc
|
||||
# Excavation
|
||||
exc_ref=np.vstack([REF['EXC_crown'],REF['EXC_haunchL'],REF['EXC_haunchR']])
|
||||
keepE_L,eL,reL=side_clip(exc_ref,E,'L'); keepE_R,eR,reR=side_clip(exc_ref,E,'R')
|
||||
out['EXC_arcs']=dist_stats(E, np.vstack([REF['EXC_crown'],keepE_L,keepE_R]),'EXC')
|
||||
out['EXC_arcs']['clipped_below_model_extent']={'L':eL,'R':eR,'yrange_L':reL,'yrange_R':reR}
|
||||
tE=cKDTree(E); d,_=tE.query(REF['EXC_crown']); out['EXC_crown_ref2model_max_mm']=round(float(d.max()),1)
|
||||
# Pavement: top/bottom/sides
|
||||
pav_ref=np.vstack([REF['PAV_top'],REF['PAV_bot'],REF['PAV_sideL'],REF['PAV_sideR']])
|
||||
out['PAV']=dist_stats(P[(P[:,0]>9526.3-150)&(P[:,0]<18726.3+150)], pav_ref,'PAV')
|
||||
# Base: model points restricted to road width (drawn span)
|
||||
Bw=B[(B[:,0]>=9526.3-1)&(B[:,0]<=18726.3+1)]
|
||||
out['BASE_drawn_span']=dist_stats(Bw, np.vstack([REF['BASE_top'],REF['BASE_bot']]),'BASE')
|
||||
out['BASE_model_extent_x']=[round(float(B[:,0].min())),round(float(B[:,0].max()))]
|
||||
out['BASE_model_ymin_ymax']=[round(float(B[:,1].min())),round(float(B[:,1].max()))]
|
||||
# Drains: both ditches vs both references
|
||||
dref=np.vstack([REF[k] for k in ['DRN_wallL1','DRN_wallL2','DRN_botL','DRN_pipeL','DRN_wallR1','DRN_wallR2','DRN_botR','DRN_pipeR']])
|
||||
dm=D[(D[:,0]>8900)&(D[:,0]<19300)]
|
||||
out['DRN']=dist_stats(dm, dref,'DRN')
|
||||
# per-ditch: ref walls/bottom/pipe to model
|
||||
tD=cKDTree(dm)
|
||||
out['DRN_perref_ref2model_max_mm']={k: round(float(tD.query(REF[k])[0].max()),1) for k in ['DRN_wallL1','DRN_wallL2','DRN_botL','DRN_pipeL','DRN_wallR1','DRN_wallR2','DRN_botR','DRN_pipeR']}
|
||||
out['DRN_model_xrange']=[round(float(dm[:,0].min())),round(float(dm[:,0].max()))]
|
||||
out['DRN_model_yrange']=[round(float(dm[:,1].min())),round(float(dm[:,1].max()))]
|
||||
return out
|
||||
allres={}
|
||||
for sig in [1,-1]:
|
||||
for st in ['4745.0005','5100.0','5469.9995']:
|
||||
r=run(sig,st,None); allres[f"{sig}|{st}"]=r
|
||||
print(f"=== sigma={sig} st={st} slope={FIT[st]['slope_top']*sig:.5f}")
|
||||
for k in ['LIN_arcs','EXC_arcs','PAV','BASE_drawn_span','DRN']:
|
||||
v=r[k];
|
||||
if v is None: print(' ',k,'none'); continue
|
||||
print(f" {k:16s} ref->model max={v['ref_to_model_max']*1000:7.1f}mm mean={v['ref_to_model_mean']*1000:6.1f} | model->ref max={v['model_to_ref_max']*1000:7.1f}mm mean={v['model_to_ref_mean']*1000:6.1f}")
|
||||
if 'clipped_below_model_extent' in v: print(' clipped', v['clipped_below_model_extent'])
|
||||
print(' LIN foot lines model->ref / ref->model:', None if r['LIN_footlines'] is None else (round(r['LIN_footlines']['model_to_ref_max']*1000,1), round(r['LIN_footlines']['ref_to_model_max']*1000,1)))
|
||||
print(' LIN per-arc ref->model max mm:', r['LIN_perarc_ref2model_max_mm'])
|
||||
print(' EXC crown ref->model max mm:', r['EXC_crown_ref2model_max_mm'])
|
||||
print(' BASE model x-range', r['BASE_model_extent_x'], 'y-range', r['BASE_model_ymin_ymax'])
|
||||
print(' DRN per-ref ref->model max mm:', r['DRN_perref_ref2model_max_mm'], 'model x', r['DRN_model_xrange'], 'y', r['DRN_model_yrange'])
|
||||
json.dump(allres, open('/tmp/verify_daesan2_run/hd2_results.json','w'), indent=1, default=str)
|
||||
@@ -0,0 +1,44 @@
|
||||
import sys, json, math
|
||||
sys.path += ['/root/.local/lib/python3.13/site-packages','/usr/local/lib/python3.13/dist-packages','/usr/lib/python3/dist-packages']
|
||||
import numpy as np
|
||||
from scipy.spatial import cKDTree
|
||||
from scipy.optimize import least_squares
|
||||
import matplotlib; matplotlib.use('Agg'); import matplotlib.pyplot as plt
|
||||
SEC = json.load(open('/tmp/verify_daesan2_run/sections_final.json'))
|
||||
def pts_of(st, pred):
|
||||
out=[]; ids=[]
|
||||
for sid,v in SEC[st]['solids'].items():
|
||||
if pred(v['group'], v['id']):
|
||||
for poly in v['polys']:
|
||||
out.append(np.array(poly)[:, :2]); ids.append(v['id'])
|
||||
return (np.vstack(out) if out else np.zeros((0,2))), ids
|
||||
def fit_circle(P):
|
||||
x,y=P[:,0],P[:,1]
|
||||
A=np.c_[2*x,2*y,np.ones(len(x))]; b=x*x+y*y
|
||||
c,_,_,_=np.linalg.lstsq(A,b,rcond=None)
|
||||
xc,yc=c[0],c[1]; r=math.sqrt(c[2]+xc*xc+yc*yc)
|
||||
return xc,yc,r
|
||||
# ---- lining crown fit per station
|
||||
fits={}
|
||||
for st in SEC:
|
||||
P,_=pts_of(st, lambda g,i: g=='라이닝')
|
||||
u0=(P[:,0].min()+P[:,0].max())/2; v0=P[:,1].max()-7.282
|
||||
d=np.hypot(P[:,0]-u0,P[:,1]-v0)
|
||||
sel_o=P[(np.abs(d-7.282)<0.03)&(P[:,1]-v0>4.0)]
|
||||
sel_i=P[(np.abs(d-6.982)<0.03)&(P[:,1]-v0>4.0)]
|
||||
co=fit_circle(sel_o); ci=fit_circle(sel_i)
|
||||
fits[st]={'outer':co,'inner':ci,'n_o':len(sel_o),'n_i':len(sel_i)}
|
||||
print(f"st {st}: outer n={len(sel_o)} centre=({co[0]:.4f},{co[1]:.4f}) R={co[2]:.4f} | inner n={len(sel_i)} centre=({ci[0]:.4f},{ci[1]:.4f}) R={ci[2]:.4f}")
|
||||
# ---- pavement slope (top/bottom envelopes) per station
|
||||
for st in SEC:
|
||||
P,_=pts_of(st, lambda g,i: g=='노면(계획고)')
|
||||
bins=np.arange(P[:,0].min(),P[:,0].max()+0.25,0.25)
|
||||
tops=[];bots=[]
|
||||
for a,b in zip(bins[:-1],bins[1:]):
|
||||
m=(P[:,0]>=a)&(P[:,0]<b)
|
||||
if m.sum()>0: tops.append((P[m,0].mean(),P[m,1].max())); bots.append((P[m,0].mean(),P[m,1].min()))
|
||||
tops=np.array(tops); bots=np.array(bots)
|
||||
mt=np.polyfit(tops[:,0],tops[:,1],1); mb=np.polyfit(bots[:,0],bots[:,1],1)
|
||||
fits[st]['slope_top']=mt[0]; fits[st]['slope_bot']=mb[0]
|
||||
print(f"st {st}: PAV top slope dv/du={mt[0]:.5f} (S={mt[0]*100:.3f}%), bottom slope={mb[0]:.5f}; top@u=0 {mt[1]:.4f}, bottom@u=0 {mb[1]:.4f}; u-range [{P[:,0].min():.3f},{P[:,0].max():.3f}]")
|
||||
json.dump(fits, open('/tmp/verify_daesan2_run/fits.json','w'))
|
||||
@@ -0,0 +1,77 @@
|
||||
import sys, json, pickle, math
|
||||
sys.path += ['/root/.local/lib/python3.13/site-packages','/usr/local/lib/python3.13/dist-packages','/usr/lib/python3/dist-packages']
|
||||
from build123d import import_step, Edge, Shape
|
||||
from OCP.BRepAlgoAPI import BRepAlgoAPI_Section
|
||||
from OCP.gp import gp_Pln, gp_Pnt, gp_Dir
|
||||
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeFace
|
||||
from OCP.TopExp import TopExp_Explorer
|
||||
from OCP.TopAbs import TopAbs_EDGE
|
||||
from OCP.TopoDS import TopoDS
|
||||
import numpy as np
|
||||
solids = import_step("/mnt/user-data/uploads/97.과제/Code/3d-drawing/model/tunnel_solids.step").solids()
|
||||
mp = pickle.load(open('/tmp/verify_daesan2_run/mapping.pkl','rb'))['mapping']
|
||||
caps = json.load(open('/tmp/verify_daesan2_run/caps.json'))
|
||||
ch = json.load(open('/tmp/verify_daesan2_run/chain.json'))
|
||||
lin = ch['lin']
|
||||
starts = {k: np.array(v) for k,v in ch['starts'].items()}
|
||||
ends = {k: np.array(v) for k,v in ch['ends'].items()}
|
||||
# station knots
|
||||
S = [caps[k]['from_sta'] for k in lin] + [caps[lin[-1]]['to_sta']]
|
||||
P = [starts[k] for k in lin] + [ends[lin[-1]]]
|
||||
def plane_at(s):
|
||||
for i in range(len(lin)):
|
||||
if S[i] <= s <= S[i+1]:
|
||||
f=(s-S[i])/(S[i+1]-S[i])
|
||||
Q=(1-f)*P[i]+f*P[i+1]
|
||||
T=(P[i+1]-P[i])[:2]; T=T/np.linalg.norm(T)
|
||||
return Q, T, lin[i]
|
||||
raise ValueError(s)
|
||||
def slice_solid(idx, Q, T):
|
||||
nrm=np.array([T[0],T[1],0.0])
|
||||
pl=gp_Pln(gp_Pnt(*Q), gp_Dir(*nrm))
|
||||
face=BRepBuilderAPI_MakeFace(pl,-80,80,-80,80).Face()
|
||||
sec=BRepAlgoAPI_Section(solids[idx].wrapped, face); sec.Build()
|
||||
sh=sec.Shape()
|
||||
polys=[]
|
||||
exp=TopExp_Explorer(sh, TopAbs_EDGE)
|
||||
while exp.More():
|
||||
e=Shape.cast(exp.Current())
|
||||
L=e.length; N=max(2,int(L/0.02))
|
||||
pts=[e.position_at(i/N) for i in range(N+1)]
|
||||
polys.append([[p.X,p.Y,p.Z] for p in pts])
|
||||
exp.Next()
|
||||
return polys
|
||||
out={}
|
||||
stations=[4745.0005, 5100.0, 5469.9995]
|
||||
for s in stations:
|
||||
Q,T,lk=plane_at(s)
|
||||
eh=np.array([-T[1],T[0]])
|
||||
print(f"== station {s}: comp {lk}, Q=({Q[0]:.4f},{Q[1]:.4f},{Q[2]:.4f}) T=({T[0]:.5f},{T[1]:.5f}) ")
|
||||
out[str(s)]={'Q':Q.tolist(),'T':T.tolist(),'eh':eh.tolist(),'comps':{}}
|
||||
for cid,info in caps.items():
|
||||
if not (info['from_sta'] <= s <= info['to_sta']): continue
|
||||
polys=slice_solid(mp[cid], Q, T)
|
||||
uv=[]
|
||||
for pl_ in polys:
|
||||
arr=np.array(pl_)
|
||||
dx=arr[:,:2]-Q[:2]
|
||||
u=dx@eh; v=arr[:,2]
|
||||
uv.append(np.stack([u,v],1).tolist())
|
||||
out[str(s)]['comps'][cid]={'group':info['group'],'polys':uv}
|
||||
allp=np.vstack([np.array(x) for x in uv]) if uv else np.zeros((0,2))
|
||||
if info['group'] in ('라이닝','굴착면','노면(계획고)','기층·인버트(가정)','공동구(가정)') or cid.startswith('DRN') or 'DRN' in cid or 'HI' in cid:
|
||||
pass
|
||||
# summary per group
|
||||
import collections
|
||||
summ=collections.defaultdict(list)
|
||||
for cid,v in out[str(s)]['comps'].items():
|
||||
summ[v['group']].append(cid)
|
||||
for g,cids in summ.items():
|
||||
allp=[p for cid in cids for poly in out[str(s)]['comps'][cid]['polys'] for p in poly]
|
||||
A=np.array(allp) if allp else np.zeros((0,2))
|
||||
if len(A):
|
||||
print(f" {g}: n_comp={len(cids)} n_pts={len(A)} u[{A[:,0].min():.3f},{A[:,0].max():.3f}] v[{A[:,1].min():.3f},{A[:,1].max():.3f}] ids={cids[:4]}")
|
||||
else:
|
||||
print(f" {g}: n_comp={len(cids)} EMPTY ids={cids[:4]}")
|
||||
json.dump(out, open('/tmp/verify_daesan2_run/sections_raw.json','w'))
|
||||
print("saved sections_raw.json")
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Step 3 cross-check of the lining deliverables (Task 6).
|
||||
Re-reads the raw DXF texts (04-004..009 and 03-001) with a plain text search,
|
||||
independent of extract_lining_spec.py, and compares them with the report and
|
||||
the model meta. Prints PASS/FAIL per check with population counts.
|
||||
Usage: verify_lining.py <RAG_ROOT> <DOC_LINING_MD> <DOC_STD_MD> <META_JSON>
|
||||
"""
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import ezdxf
|
||||
from ezdxf import recover
|
||||
|
||||
RAG, DOC_LIN, DOC_STD, META = sys.argv[1:5]
|
||||
RESULTS = []
|
||||
|
||||
|
||||
def check(name, ok, detail):
|
||||
RESULTS.append((name, ok, detail))
|
||||
print(f"[{'PASS' if ok else 'FAIL'}] {name}: {detail}")
|
||||
|
||||
|
||||
def all_texts(path):
|
||||
try:
|
||||
doc = ezdxf.readfile(path)
|
||||
except Exception:
|
||||
doc, _ = recover.readfile(path)
|
||||
out = []
|
||||
for e in doc.modelspace():
|
||||
if e.dxftype() == "TEXT":
|
||||
out.append(e.dxf.text)
|
||||
elif e.dxftype() == "MTEXT":
|
||||
out.append(e.plain_text())
|
||||
return out
|
||||
|
||||
|
||||
sheets = sorted(glob.glob(os.path.join(RAG, "04_본선터널기타", "C0060304-00[4-9]*.dxf")))
|
||||
std = glob.glob(os.path.join(RAG, "03_본선터널굴착", "C0060303-001*.dxf"))
|
||||
raw = {os.path.basename(p)[:14]: all_texts(p) for p in sheets}
|
||||
doc_lin = open(DOC_LIN, encoding="utf-8").read()
|
||||
doc_std = open(DOC_STD, encoding="utf-8").read()
|
||||
meta = json.load(open(META, encoding="utf-8"))
|
||||
|
||||
# C1 common values present in every sheet (population = 6 sheets)
|
||||
common = {"fck=27MPa": "fck 27", "fy=400MPa": "SD400", "최대치수 Ø25": "Ø25",
|
||||
"R1=6.892": "R1 6.892", "R2=3,712": "R2 3,712", "R2'=3,910": "R2' 3,910",
|
||||
"EL.-0.367": "EL -0.367", "EL. 0.000": "EL 0.000"}
|
||||
for token, label in common.items():
|
||||
hits = [k for k, t in raw.items() if any(token in s for s in t)]
|
||||
check(f"C1 {label} in all sheets", len(hits) == len(raw), f"{len(hits)}/{len(raw)} sheets (표본 아님, 전수)")
|
||||
|
||||
# C2 temperature-not-considered detail appears on 3 sheets (005, 006, 007)
|
||||
temp = [k for k, t in raw.items() if any("온도미고려" in s for s in t)]
|
||||
check("C2 온도미고려 detail sheets", sorted(temp) == sorted([k for k in raw if k[9:12] in ("005", "006", "007")]),
|
||||
f"{len(temp)} sheets: {[k[9:12] for k in sorted(temp)]} (전수)")
|
||||
|
||||
# C3 pattern-group count in raw texts equals report rows (9)
|
||||
groups = [s for t in raw.values() for s in t if "패턴" in s and "주철근 조립도" in s or (("패턴)" in s) and ("입출구부" in s or "온도미고려" in s))]
|
||||
rows = len([l for l in doc_lin.splitlines() if l.startswith("| C0060304-")])
|
||||
check("C3 pattern-detail count = report rows", len(groups) == rows,
|
||||
f"raw {len(groups)} vs report {rows} (전수)")
|
||||
|
||||
# C4 R1 cross-check against the standard section drawing and the report table
|
||||
std_txt = all_texts(std[0]) if std else []
|
||||
std_has = any(("6.982" in s or "6982" in s or "6,982" in s) for s in std_txt)
|
||||
check("C4 R1 6.982 in standard section (03-001 raw)", std_has, f"03-001 raw texts scanned: {len(std_txt)}")
|
||||
check("C4b standard table DOC states R1 6,982", "6,982" in doc_std, "설계_표준단면치수표 본문 대조")
|
||||
check("C4c report records decision", "6.982 m 유지" in doc_lin, "R1 결정 문구 존재")
|
||||
|
||||
# C5 thickness arithmetic: outer R 7.282 m minus inner R
|
||||
t982 = round(7.282 - 6.982, 3); t892 = round(7.282 - 6.892, 3)
|
||||
check("C5 thickness arithmetic", t982 == 0.300 and t892 == 0.390,
|
||||
f"7.282-6.982={t982} m (matches 300 mm), 7.282-6.892={t892} m (does not match)")
|
||||
|
||||
# C6 V9 existence + name version + key content of OUTPUT
|
||||
name = os.path.basename(DOC_LIN)
|
||||
m = re.match(r"^설계_콘크리트라이닝상세_(\d{4}-\d{2}-\d{2})_v(\d+\.\d+\.\d{2})\.md$", name)
|
||||
ver_hdr = re.search(r"\| (v\d+\.\d+\.\d{2}) \|", doc_lin)
|
||||
check("C6 file name pattern and version", bool(m) and ver_hdr is not None and ver_hdr.group(1) == "v" + m.group(2),
|
||||
f"name v{m.group(2) if m else '?'} vs header {ver_hdr.group(1) if ver_hdr else '?'}")
|
||||
check("C6b OUTPUT key items present", all(k in doc_lin for k in ["fck", "SD400", "입출구부", "확정 2026-10-21"]),
|
||||
"fck, SD400, 입출구부, 결정 문구")
|
||||
|
||||
# C7 meta: lining reference and open items
|
||||
lr = meta.get("lining_reference", {})
|
||||
check("C7 meta lining_reference 9 groups", sum(len(s["groups"]) for s in lr.get("sheets", [])) == 9,
|
||||
f"{sum(len(s['groups']) for s in lr.get('sheets', []))} groups, status={lr.get('summary', '')[:40]}...")
|
||||
check("C7b meta open records decision", any("확정 2026-10-21" in o for o in meta.get("open", [])), "open[] 결정 문구")
|
||||
|
||||
# C8 scripts never write to the RAG source tree
|
||||
writes = []
|
||||
for sc in glob.glob("scripts/*lining*.py"):
|
||||
txt = open(sc, encoding="utf-8").read()
|
||||
if re.search(r"open\([^)]*RAG[^)]*['\"]w", txt):
|
||||
writes.append(sc)
|
||||
check("C8 no script writes into RAG", not writes, f"scanned {len(glob.glob('scripts/*lining*.py'))} scripts")
|
||||
|
||||
n_fail = sum(1 for r in RESULTS if not r[1])
|
||||
print(f"SUMMARY: {len(RESULTS) - n_fail} PASS / {n_fail} FAIL of {len(RESULTS)}")
|
||||
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Step 3 deterministic checks for the solid model (Task 8, 대산2본선3D변환).
|
||||
|
||||
Usage: python3 verify_solids.py <project_dir> <pattern_csv>
|
||||
project_dir : Code/3d-drawing folder (contains model/ and data/)
|
||||
pattern_csv : data/pattern_stations_dangjin.csv
|
||||
|
||||
Checks (each prints PASS/FAIL with counts, method and population):
|
||||
M1 meta counts per group M2 pattern runs contiguous 4745..5480
|
||||
M3 runs == CSV rows (in range) M4 component ids and from/to/pattern match runs
|
||||
M5 meta volume sum == verify json M6 pattern-run ids (PAV/BASE/DRN/HBOX/IBOX) present
|
||||
S1 STEP re-import: solid count, validity, total volume vs meta (0.1 %)
|
||||
S2 lining vs excavation overlap per run (volume, threshold 0.05 m2 x run length)
|
||||
G1 GLB reload: geometry count and non-empty meshes
|
||||
"""
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from collections import Counter, defaultdict
|
||||
|
||||
PROJ, CSV = sys.argv[1], sys.argv[2]
|
||||
S0, S1 = 4745, 5480
|
||||
results = []
|
||||
|
||||
|
||||
def rec(code, verdict, method, note=""):
|
||||
results.append((code, verdict, method, note))
|
||||
print(f"{code} {verdict} | {method} | {note}")
|
||||
|
||||
|
||||
meta = json.load(open(os.path.join(PROJ, "model/tunnel_solids_meta.json"), encoding="utf-8"))
|
||||
verify = json.load(open(os.path.join(PROJ, "data/verify_solids.json"), encoding="utf-8"))
|
||||
comps = meta["components"]
|
||||
runs = meta["pattern_runs"]
|
||||
solids_meta = [c for c in comps if not c.get("reference")]
|
||||
refs = [c for c in comps if c.get("reference")]
|
||||
|
||||
# M1 counts per group
|
||||
cnt = Counter(c["group"] for c in solids_meta)
|
||||
exp = {"라이닝": 40, "굴착면": 40, "노면(계획고)": 40, "기층·인버트(가정)": 40, "배수(측구)": 80, "공동구(가정)": 80}
|
||||
bad = {g: (cnt.get(g, 0), n) for g, n in exp.items() if cnt.get(g, 0) != n}
|
||||
rec("M1", "PASS" if not bad and len(solids_meta) == 320 and len(refs) == 6 else "FAIL",
|
||||
f"meta components 전수 {len(comps)}", f"solids {len(solids_meta)} (expect 320), refs {len(refs)} (expect 6), group mismatch {bad}")
|
||||
|
||||
# M2 contiguity
|
||||
runs_sorted = sorted(runs, key=lambda r: r["from"])
|
||||
gaps = [(a["to"], b["from"]) for a, b in zip(runs_sorted, runs_sorted[1:]) if a["to"] != b["from"]]
|
||||
ok2 = runs_sorted[0]["from"] == S0 and runs_sorted[-1]["to"] == S1 and not gaps
|
||||
rec("M2", "PASS" if ok2 else "FAIL", f"pattern_runs 전수 {len(runs)}",
|
||||
f"range {runs_sorted[0]['from']}..{runs_sorted[-1]['to']}, gaps/overlaps {gaps}")
|
||||
|
||||
# M3 runs vs CSV rows inside the model range
|
||||
rows = list(csv.DictReader(open(CSV, encoding="utf-8")))
|
||||
csv_in = [r for r in rows if float(r["sta_to"]) > S0 and float(r["sta_from"]) < S1]
|
||||
# clip each CSV interval to the model range (row 95 runs 5468..5495 and is cut at 5480 by design)
|
||||
csv_set = Counter((max(int(float(r["sta_from"])), S0), min(int(float(r["sta_to"])), S1), r["lining_pattern"]) for r in csv_in)
|
||||
run_set = Counter((r["from"], r["to"], r["pattern"]) for r in runs)
|
||||
mism = (csv_set - run_set) + (run_set - csv_set)
|
||||
rec("M3", "PASS" if not mism else "FAIL",
|
||||
f"CSV 전수 {len(rows)} 행 중 범위 내 {len(csv_in)} 행과 pattern_runs {len(runs)} 행 대조",
|
||||
f"mismatch {dict(mism)}")
|
||||
|
||||
# M4 component from/to/pattern equal their run
|
||||
run_by_id = {r["id"]: r for r in runs}
|
||||
bad4 = []
|
||||
for c in solids_meta:
|
||||
if c["group"] == "라이닝":
|
||||
r = run_by_id.get(c["id"])
|
||||
if not r or (r["from"], r["to"], r["pattern"]) != (c["from_sta"], c["to_sta"], c["pattern"]):
|
||||
bad4.append(c["id"])
|
||||
rec("M4", "PASS" if not bad4 else "FAIL", f"라이닝 전수 {cnt['라이닝']} 대 run",
|
||||
f"mismatch {bad4[:5]}")
|
||||
|
||||
# M5 volume sum
|
||||
vol = sum(c["volume_m3"] for c in solids_meta)
|
||||
ok5 = abs(vol - verify["volume_sum_m3"]) < 0.001
|
||||
rec("M5", "PASS" if ok5 else "FAIL", f"meta 부피 합 전수 {len(solids_meta)} 개",
|
||||
f"sum {vol:.3f} vs verify {verify['volume_sum_m3']}")
|
||||
|
||||
# M6 per-run component ids
|
||||
need = ["LIN", "EXC", "PAV", "BASE", "DRNL", "DRNR", "HBOX", "IBOX"]
|
||||
ids = {c["id"] for c in solids_meta}
|
||||
miss = []
|
||||
for i, r in enumerate(runs_sorted):
|
||||
for pre in need:
|
||||
cid = f"{pre}_{i+1:02d}_{r['pattern']}"
|
||||
if pre in ("DRNL", "DRNR"):
|
||||
cid = f"{pre}_{i+1:02d}_{r['pattern']}"
|
||||
if cid not in ids and pre not in ("DRNL", "DRNR"):
|
||||
miss.append(cid)
|
||||
drn = [c for c in solids_meta if c["group"] == "배수(측구)"]
|
||||
rec("M6", "PASS" if not miss and len(drn) == 80 else "FAIL",
|
||||
f"run {len(runs_sorted)} x 부품 종류 {len(need)} 대조 (배수 L/R 포함 {len(drn)})",
|
||||
f"missing {miss[:5]}")
|
||||
|
||||
# S1 STEP re-import
|
||||
import build123d as bd # noqa: E402
|
||||
step_path = os.path.join(PROJ, "model/tunnel_solids.step")
|
||||
shape = bd.importers.import_step(step_path) if hasattr(bd, "importers") else bd.import_step(step_path)
|
||||
solids = shape.solids()
|
||||
n_valid = sum(1 for s in solids if s.is_valid)
|
||||
vol_step = sum(s.volume for s in solids)
|
||||
rel = abs(vol_step - vol) / vol
|
||||
rec("S1", "PASS" if len(solids) == 320 and n_valid == len(solids) and rel < 0.001 else "FAIL",
|
||||
f"STEP 재임포트 솔리드 전수 {len(solids)}, is_valid 전수 {n_valid}, 부피 재계산 합 vs meta",
|
||||
f"count {len(solids)}, valid {n_valid}, vol_step {vol_step:.3f} vs meta {vol:.3f} (rel {rel:.2e})")
|
||||
|
||||
# S2 overlap lining vs excavation per run, using the STEP solids
|
||||
def centroid(s):
|
||||
return s.center()
|
||||
|
||||
lin = [s for s in solids]
|
||||
# classify STEP solids by volume match to meta (only for overlap sampling)
|
||||
meta_vol = sorted([(c["volume_m3"], c["group"], c["id"], c["from_sta"], c["to_sta"]) for c in solids_meta])
|
||||
sample_ids = [r["id"] for r in runs_sorted[:: max(1, len(runs_sorted) // 4)]][:4]
|
||||
lines = []
|
||||
ok_overlap = True
|
||||
for rid in sample_ids:
|
||||
r = run_by_id[rid]
|
||||
k = rid.split("_", 1)[1]
|
||||
idx = [i for i, rr in enumerate(runs_sorted) if rr["id"] == rid][0] + 1
|
||||
lin_c = next(c for c in solids_meta if c["id"] == f"LIN_{idx:02d}_{r['pattern']}")
|
||||
exc_c = next(c for c in solids_meta if c["id"] == f"EXC_{idx:02d}_{r['pattern']}")
|
||||
def find(vol_target):
|
||||
best = min(solids, key=lambda s: abs(s.volume - vol_target))
|
||||
assert abs(best.volume - vol_target) < 1e-3, "no STEP solid matches meta volume"
|
||||
return best
|
||||
sl = find(lin_c["volume_m3"]); se = find(exc_c["volume_m3"])
|
||||
inter = sl.intersect(se)
|
||||
iv = sum(x.volume for x in inter) if inter is not None else 0.0
|
||||
length = r["to"] - r["from"]
|
||||
thr = 0.05 * length
|
||||
lines.append(f"{rid}: ovl {iv:.4f} m3 (thr {thr:.2f})")
|
||||
if iv > thr:
|
||||
ok_overlap = False
|
||||
rec("S2", "PASS" if ok_overlap else "FAIL", f"샘플 run {len(sample_ids)}건 라이닝∩굴착 부피 (부피 매칭 솔리드)",
|
||||
"; ".join(lines))
|
||||
|
||||
# G1 GLB reload
|
||||
import trimesh # noqa: E402
|
||||
glb = trimesh.load(os.path.join(PROJ, "model/tunnel_solids.glb"), force="scene")
|
||||
geoms = list(glb.geometry.values())
|
||||
empty = [g for g in geoms if len(g.faces) == 0]
|
||||
rec("G1", "PASS" if len(geoms) == verify["glb_geometries"] and not empty else "FAIL",
|
||||
f"GLB 로드 지오메트리 전수 {len(geoms)}", f"empty {len(empty)}, expect {verify['glb_geometries']}")
|
||||
|
||||
fails = [r for r in results if r[1] == "FAIL"]
|
||||
print("SUMMARY", len(results), "checks, FAIL", len(fails))
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Headless browser check of the 3D viewer (Task 6, V11).
|
||||
Needs Playwright + Chromium and the viewer/model served over HTTP, e.g.
|
||||
python3 -m http.server 8765 (from Code/3d-drawing)
|
||||
python3 verify_viewer_headless.py http://127.0.0.1:8765/viewer/index.html <chromium_path>
|
||||
Checks: page loads without page errors, canvas exists, meta text is shown,
|
||||
and a click on the lining (other layers hidden) returns station and pattern.
|
||||
"""
|
||||
import sys
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
URL, CHROME = sys.argv[1], sys.argv[2]
|
||||
with sync_playwright() as p:
|
||||
b = p.chromium.launch(executable_path=CHROME, args=["--use-gl=swiftshader", "--enable-webgl", "--ignore-gpu-blocklist", "--no-sandbox"])
|
||||
pg = b.new_page(viewport={"width": 1280, "height": 800})
|
||||
errors = []
|
||||
pg.on("pageerror", lambda e: errors.append(str(e)))
|
||||
pg.goto(URL, wait_until="networkidle", timeout=90000)
|
||||
pg.wait_for_timeout(6000)
|
||||
canvas = pg.evaluate("() => !!document.querySelector('canvas')")
|
||||
body = pg.evaluate("() => document.body.innerText")
|
||||
print("page errors:", errors)
|
||||
print("canvas present:", canvas, "| meta shown:", "패턴:" in body and "범위:" in body)
|
||||
for label in ["excavation_R7402", "road_surface", "ground_GL"]:
|
||||
pg.locator("label", has_text=label).locator("input").uncheck()
|
||||
pg.wait_for_timeout(500)
|
||||
readout = None
|
||||
for fx in [0.5, 0.45, 0.55, 0.4, 0.6, 0.35, 0.65]:
|
||||
for fy in [0.5, 0.45, 0.55, 0.4, 0.6]:
|
||||
pg.mouse.click(1280 * fx, 800 * fy)
|
||||
pg.wait_for_timeout(200)
|
||||
t = pg.evaluate("() => { const els=[...document.querySelectorAll('div,pre,p')]; return els.map(e=>e.innerText).filter(s=>s && s.includes('교차점')).pop()||null }")
|
||||
if t and "측점" in t:
|
||||
readout = t
|
||||
break
|
||||
if readout:
|
||||
break
|
||||
print("lining readout:", (readout or "NO LINING HIT").replace("\n", " | ")[:700])
|
||||
b.close()
|
||||
@@ -0,0 +1,670 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>대산2터널 본선 3D 모델</title>
|
||||
<style>
|
||||
/* ── Prism CSS 변수 정본 (webapp/module.webapp.md §3 탑재, 수정 금지) ── */
|
||||
:root {
|
||||
/* ── color (light) ─────────────────────────────── */
|
||||
--prism-bg-canvas: #F4F3F0; /* sand G4 — 순백 전면 금지 */
|
||||
--prism-bg-card: #FFFFFF; /* white G0 */
|
||||
--prism-text-body: #474235; /* brown G72 — on sand 9.02 AAA */
|
||||
--prism-text-heading: #1F524B; /* green G69 — on sand 8.00 AAA */
|
||||
--prism-text-subtle: #474235; /* 저강조는 색이 아니라 크기·웨이트로 */
|
||||
--prism-text-accent: #D05C26; /* orange G47 — 대형 전용(24px+/19px+Bold) */
|
||||
--prism-border-default: #474235;
|
||||
--prism-border-strong: #1F524B;
|
||||
--prism-status-do: #005A4E; --prism-status-do-bg: #DCEAE6;
|
||||
--prism-status-dont: #8F2A11; --prism-status-dont-bg: #F6DFDA;
|
||||
--prism-status-caution: #6B4A00; --prism-status-caution-bg: #F5E7CC;
|
||||
--prism-status-tip: #0A4E86; --prism-status-tip-bg: #DDE8F3;
|
||||
--prism-dv-1: #C08A2E; --prism-dv-2: #6E8F3C; --prism-dv-3: #3C6E9F;
|
||||
--prism-dv-4: #8C3B14; --prism-dv-5: #1B4A43; --prism-dv-6: #5E4370;
|
||||
|
||||
/* ── space (base 8px = 0.5rem) ──────────────────── */
|
||||
--prism-space-025: 0.125rem; /* 2px */
|
||||
--prism-space-050: 0.25rem; /* 4px */
|
||||
--prism-space-100: 0.5rem; /* 8px */
|
||||
--prism-space-150: 0.75rem; /* 12px */
|
||||
--prism-space-200: 1rem; /* 16px — card padding */
|
||||
--prism-space-300: 1.5rem; /* 24px — block gap */
|
||||
--prism-space-400: 2rem; /* 32px — zone gap */
|
||||
--prism-space-600: 3rem; /* 48px */
|
||||
--prism-space-800: 4rem; /* 64px */
|
||||
|
||||
/* ── type (root 16px 미고정 — html font-size 선언 금지) ── */
|
||||
--prism-type-caption: 0.8125rem; --prism-lh-caption: 1.55;
|
||||
--prism-type-body-sm: 0.875rem; --prism-lh-body: 1.70;
|
||||
--prism-type-body: 1rem;
|
||||
--prism-type-body-lg: 1.125rem;
|
||||
--prism-type-title-sm: 1.25rem; --prism-lh-title: 1.50;
|
||||
--prism-type-title: 1.625rem;
|
||||
--prism-type-title-lg: 2rem;
|
||||
--prism-type-display: 2.875rem; --prism-lh-display: 1.25;
|
||||
--prism-type-display-lg: 4.125rem;
|
||||
|
||||
/* ── radius · stroke · z · motion ───────────────── */
|
||||
--prism-radius-sm: 4px; --prism-radius-md: 8px; --prism-radius-lg: 12px;
|
||||
--prism-radius-pill: 9999px;
|
||||
--prism-stroke-hairline: 1px; --prism-stroke-regular: 2px; --prism-stroke-bold: 3px;
|
||||
--prism-z-raised: 100; --prism-z-sticky: 200; --prism-z-dropdown: 300;
|
||||
--prism-z-scrim: 400; --prism-z-modal: 500; --prism-z-popover: 600;
|
||||
--prism-z-toast: 700; --prism-z-tooltip: 800;
|
||||
--prism-scrim: rgba(0, 0, 0, 0.32);
|
||||
--prism-motion-fast: 100ms; --prism-motion-moderate: 200ms; --prism-motion-slow: 300ms;
|
||||
--prism-ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||
--prism-ease-enter: cubic-bezier(0, 0, 0, 1);
|
||||
--prism-ease-exit: cubic-bezier(0.3, 0, 1, 1);
|
||||
--prism-focus-ring: 3px; --prism-focus-offset: 2px;
|
||||
--prism-measure-max: 38em;
|
||||
}
|
||||
|
||||
/* ── dark theme (data-theme + OS 연동) ─────────────── */
|
||||
:root[data-theme="dark"] {
|
||||
--prism-bg-canvas: #111111; /* obsidian G95 */
|
||||
--prism-bg-card: #111111; /* 카드는 보더로 분리 */
|
||||
--prism-text-body: #F4F3F0; /* sand on obsidian 17.02 AAA */
|
||||
--prism-text-heading: #F4F3F0; /* ⚠ green 텍스트 금지 (2.13:1) — 면·도형만 */
|
||||
--prism-text-subtle: #D3D3D3; /* subtle 12.61 AAA */
|
||||
--prism-text-accent: #D05C26; /* 4.71 AA — 대형 권장 */
|
||||
--prism-border-default: #D3D3D3;
|
||||
--prism-border-strong: #F4F3F0;
|
||||
/* 상태색 다크: container↔on 반전 (tokens v0.1.1, 실측 동일) */
|
||||
--prism-status-do: #DCEAE6; --prism-status-do-bg: #005A4E;
|
||||
--prism-status-dont: #F6DFDA; --prism-status-dont-bg: #8F2A11;
|
||||
--prism-status-caution: #F5E7CC; --prism-status-caution-bg: #6B4A00;
|
||||
--prism-status-tip: #DDE8F3; --prism-status-tip-bg: #0A4E86;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) { /* 위 dark 값셋 동일 적용 */
|
||||
--prism-bg-canvas: #111111; --prism-bg-card: #111111;
|
||||
--prism-text-body: #F4F3F0; --prism-text-heading: #F4F3F0;
|
||||
--prism-text-subtle: #D3D3D3; --prism-text-accent: #D05C26;
|
||||
--prism-border-default: #D3D3D3; --prism-border-strong: #F4F3F0;
|
||||
--prism-status-do: #DCEAE6; --prism-status-do-bg: #005A4E;
|
||||
--prism-status-dont: #F6DFDA; --prism-status-dont-bg: #8F2A11;
|
||||
--prism-status-caution: #F5E7CC; --prism-status-caution-bg: #6B4A00;
|
||||
--prism-status-tip: #DDE8F3; --prism-status-tip-bg: #0A4E86;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 베이스 (CSS-1·CSS-2: root font-size 미지정, 타입·간격 rem) ── */
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
[hidden] { display: none !important; }
|
||||
html, body { height: 100%; margin: 0; }
|
||||
body {
|
||||
display: flex; flex-direction: column; overflow: hidden;
|
||||
background: var(--prism-bg-canvas); color: var(--prism-text-body);
|
||||
font-family: Pretendard, -apple-system, BlinkMacSystemFont, "Noto Sans KR", "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
|
||||
font-size: var(--prism-type-body); line-height: var(--prism-lh-body);
|
||||
}
|
||||
h1, h2, p, dl, dd, dt { margin: 0; }
|
||||
h1 { font-size: var(--prism-type-title-sm); line-height: var(--prism-lh-title); font-weight: 700; color: var(--prism-text-heading); }
|
||||
h2 { font-size: var(--prism-type-body-sm); line-height: var(--prism-lh-title); font-weight: 700; color: var(--prism-text-heading); margin-block: var(--prism-space-150) var(--prism-space-050); }
|
||||
.rail h2:first-child { margin-block-start: 0; }
|
||||
|
||||
/* 포커스: 3px 링 + 오프셋 2px, :focus-visible 전용 (FC-1·FC-2) */
|
||||
:focus-visible { outline: var(--prism-focus-ring) solid var(--prism-text-heading); outline-offset: var(--prism-focus-offset); }
|
||||
|
||||
.skip-link {
|
||||
position: absolute; inset-inline-start: var(--prism-space-200); inset-block-start: var(--prism-space-100);
|
||||
transform: translateY(-200%); z-index: var(--prism-z-toast);
|
||||
padding: var(--prism-space-100) var(--prism-space-200); border-radius: var(--prism-radius-md);
|
||||
background: var(--prism-bg-card); color: var(--prism-text-heading); border: var(--prism-stroke-hairline) solid var(--prism-border-strong);
|
||||
}
|
||||
.skip-link:focus { transform: none; }
|
||||
|
||||
/* 헤더 — W1 셸 메타 슬롯 */
|
||||
.hdr {
|
||||
display: flex; flex-wrap: wrap; align-items: center; gap: var(--prism-space-100) var(--prism-space-400);
|
||||
padding: var(--prism-space-150) var(--prism-space-300);
|
||||
background: var(--prism-bg-card); border-bottom: var(--prism-stroke-hairline) solid var(--prism-border-default);
|
||||
}
|
||||
.meta {
|
||||
display: flex; flex-wrap: wrap; gap: var(--prism-space-050) var(--prism-space-300);
|
||||
margin-inline-start: auto; font-size: var(--prism-type-caption); line-height: var(--prism-lh-caption);
|
||||
}
|
||||
.meta div { display: flex; gap: var(--prism-space-050); }
|
||||
.meta dt { color: var(--prism-text-subtle); }
|
||||
.meta dd { color: var(--prism-text-body); font-weight: 500; font-variant-numeric: tabular-nums; }
|
||||
|
||||
/* 테마 3택 (TC-2) */
|
||||
.seg { display: inline-flex; overflow: hidden; border-radius: var(--prism-radius-md); border: var(--prism-stroke-hairline) solid var(--prism-border-default); }
|
||||
.seg button { border: 0; border-radius: 0; background: transparent; }
|
||||
.seg button + button { border-inline-start: var(--prism-stroke-hairline) solid var(--prism-border-default); }
|
||||
.seg button[aria-pressed="true"] { background: var(--prism-text-heading); color: var(--prism-bg-card); }
|
||||
|
||||
/* 셸: 좌측 내비 레일 + 뷰포트 (W1) */
|
||||
.shell { flex: 1; min-height: 0; display: grid; grid-template-columns: 17.5rem minmax(0, 1fr); }
|
||||
.rail {
|
||||
overflow: auto; padding: var(--prism-space-150);
|
||||
background: var(--prism-bg-card); border-inline-end: var(--prism-stroke-hairline) solid var(--prism-border-default);
|
||||
}
|
||||
.stage { position: relative; min-width: 0; min-height: 0; }
|
||||
#viewport { position: absolute; inset: 0; }
|
||||
#viewport canvas { display: block; }
|
||||
|
||||
/* 버튼 — primary 화면당 1개 (BT-1), 히트 영역 44px (HT-1) */
|
||||
button {
|
||||
font: inherit; font-size: var(--prism-type-body-sm); font-weight: 500; line-height: 1.2;
|
||||
min-height: 2.25rem; padding: var(--prism-space-050) var(--prism-space-100); white-space: nowrap;
|
||||
border: var(--prism-stroke-hairline) solid var(--prism-border-default); border-radius: var(--prism-radius-md);
|
||||
background: var(--prism-bg-card); color: var(--prism-text-body); cursor: pointer;
|
||||
}
|
||||
button.primary { background: var(--prism-text-heading); border-color: var(--prism-text-heading); color: var(--prism-bg-card); }
|
||||
button.quiet { background: transparent; border-color: transparent; color: var(--prism-text-heading); }
|
||||
.btn-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--prism-space-100); }
|
||||
.btn-grid .wide { grid-column: 1 / -1; }
|
||||
|
||||
/* 체크박스 행 — 히트 영역 44px */
|
||||
.opt {
|
||||
display: flex; align-items: center; gap: var(--prism-space-100);
|
||||
min-height: 2.75rem; cursor: pointer; color: var(--prism-text-body); font-size: var(--prism-type-body-sm);
|
||||
}
|
||||
.opt input[type="checkbox"] { width: 1.25rem; height: 1.25rem; margin: 0; flex: none; accent-color: var(--prism-text-heading); }
|
||||
.pats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: var(--prism-space-050); }
|
||||
.pats .opt { gap: var(--prism-space-050); white-space: nowrap; }
|
||||
.sw {
|
||||
width: var(--prism-space-200); height: var(--prism-space-200); flex: none;
|
||||
border-radius: var(--prism-radius-sm); border: var(--prism-stroke-hairline) solid var(--prism-border-default);
|
||||
}
|
||||
.sw.ghost { opacity: 0.5; }
|
||||
|
||||
/* 구간 슬라이더 — 두 핸들, 히트 영역 44px */
|
||||
.dual { position: relative; height: 2.75rem; margin-block: var(--prism-space-050); }
|
||||
.dual .track {
|
||||
position: absolute; inset-inline: var(--prism-space-150); top: 50%; height: var(--prism-stroke-regular);
|
||||
transform: translateY(-50%); border-radius: var(--prism-radius-sm); background: var(--prism-border-default);
|
||||
}
|
||||
.dual input[type="range"] {
|
||||
position: absolute; inset: 0; width: 100%; height: 100%; margin: 0;
|
||||
background: transparent; pointer-events: none; appearance: none; -webkit-appearance: none;
|
||||
}
|
||||
.dual input[type="range"]::-webkit-slider-runnable-track { background: transparent; height: 100%; }
|
||||
.dual input[type="range"]::-moz-range-track { background: transparent; }
|
||||
.dual input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none; appearance: none; pointer-events: auto; cursor: pointer;
|
||||
width: 1.25rem; height: 1.25rem; margin-block-start: var(--prism-space-150); border-radius: 50%;
|
||||
background: var(--prism-text-heading); border: var(--prism-stroke-regular) solid var(--prism-bg-card);
|
||||
}
|
||||
.dual input[type="range"]::-moz-range-thumb {
|
||||
pointer-events: auto; cursor: pointer; width: 1.25rem; height: 1.25rem; border-radius: 50%;
|
||||
background: var(--prism-text-heading); border: var(--prism-stroke-regular) solid var(--prism-bg-card);
|
||||
}
|
||||
.sta { font-size: var(--prism-type-body-sm); font-weight: 700; font-variant-numeric: tabular-nums; margin-block: var(--prism-space-050) var(--prism-space-100); }
|
||||
|
||||
|
||||
/* 개폐 (component.disclosure) — 부품 선택은 기본 접힘 */
|
||||
.disc { margin-block-start: var(--prism-space-150); border-top: var(--prism-stroke-hairline) solid var(--prism-border-default); padding-block-start: var(--prism-space-050); }
|
||||
.disc > summary {
|
||||
display: flex; align-items: center; justify-content: space-between; gap: var(--prism-space-100);
|
||||
min-height: 2.75rem; cursor: pointer; list-style: none; font-size: var(--prism-type-body-sm); font-weight: 700; color: var(--prism-text-heading);
|
||||
}
|
||||
.disc > summary::-webkit-details-marker { display: none; }
|
||||
.disc > summary > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
||||
.disc > summary::after { content: "+"; flex: none; font-weight: 500; color: var(--prism-text-subtle); }
|
||||
.disc[open] > summary::after { content: "−"; }
|
||||
.disc > summary:focus-visible { outline: var(--prism-focus-ring) solid var(--prism-text-heading); outline-offset: var(--prism-focus-offset); }
|
||||
.disc > *:not(summary) { margin-block-start: var(--prism-space-100); }
|
||||
|
||||
/* 선택 목록 */
|
||||
select {
|
||||
font: inherit; font-size: var(--prism-type-body-sm); width: 100%; min-height: 2.75rem;
|
||||
padding: var(--prism-space-050) var(--prism-space-100); border-radius: var(--prism-radius-md);
|
||||
border: var(--prism-stroke-hairline) solid var(--prism-border-default);
|
||||
background: var(--prism-bg-card); color: var(--prism-text-body);
|
||||
}
|
||||
.label { display: block; margin-block-end: var(--prism-space-050); color: var(--prism-text-subtle); font-size: var(--prism-type-caption); line-height: var(--prism-lh-caption); }
|
||||
.note { color: var(--prism-text-subtle); font-size: var(--prism-type-caption); line-height: var(--prism-lh-caption); max-inline-size: var(--prism-measure-max); margin-block-start: var(--prism-space-100); }
|
||||
.empty { color: var(--prism-text-subtle); font-size: var(--prism-type-caption); margin-block-start: var(--prism-space-100); }
|
||||
|
||||
/* 명세표 (specsheet) — 항목 말미 고정 위치, 열 구성 동일 (SS-1·SS-2) */
|
||||
.spec {
|
||||
display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--prism-space-050) var(--prism-space-150);
|
||||
font-size: var(--prism-type-caption); line-height: var(--prism-lh-caption); font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.spec dt { color: var(--prism-text-subtle); }
|
||||
.spec dd { overflow-wrap: anywhere; color: var(--prism-text-body); }
|
||||
|
||||
/* 툴팁 — overlay 단계 (z.tooltip 800, 보더 우선) */
|
||||
#tip {
|
||||
position: fixed; display: none; pointer-events: none; z-index: var(--prism-z-tooltip);
|
||||
max-inline-size: 20rem; padding: var(--prism-space-150) var(--prism-space-200);
|
||||
background: var(--prism-bg-card); color: var(--prism-text-body);
|
||||
border: var(--prism-stroke-hairline) solid var(--prism-border-strong); border-radius: var(--prism-radius-md);
|
||||
box-shadow: 0 var(--prism-space-050) var(--prism-space-150) var(--prism-scrim);
|
||||
}
|
||||
#tip .tip-title { font-weight: 700; color: var(--prism-text-heading); margin-block-end: var(--prism-space-100); }
|
||||
|
||||
/* 상호작용 — hover는 보강 수단, hover 기기에서만 (HV-3·HV-4) */
|
||||
@media (hover: hover) {
|
||||
button:not(.primary):hover { border-color: var(--prism-border-strong); }
|
||||
button.primary:hover { background: color-mix(in srgb, var(--prism-text-heading) 88%, var(--prism-text-body)); border-color: transparent; }
|
||||
button.quiet:hover { background: var(--prism-bg-canvas); }
|
||||
.opt:hover { color: var(--prism-text-heading); }
|
||||
}
|
||||
|
||||
/* 좁은 화면: 레일을 본문 아래로 (BP md 904 미만) */
|
||||
@media (max-width: 903px) {
|
||||
body { height: auto; min-height: 100%; overflow: auto; }
|
||||
.hdr { padding: var(--prism-space-150) var(--prism-space-200); }
|
||||
.meta { margin-inline-start: 0; }
|
||||
.shell { display: flex; flex-direction: column; flex: none; }
|
||||
.stage { order: -1; flex: none; height: 60vh; min-height: 20rem; }
|
||||
.rail { overflow: visible; border-inline-end: 0; border-block-start: var(--prism-stroke-hairline) solid var(--prism-border-default); }
|
||||
}
|
||||
|
||||
/* 모션: 축소 요청 시 전환 제거 (MT-3·RM-1) */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
|
||||
}
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"}}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main">본문 바로가기</a>
|
||||
|
||||
<header class="hdr">
|
||||
<h1>대산2터널 본선 3D 모델</h1>
|
||||
<dl class="meta">
|
||||
<div><dt>측점</dt><dd>4+745 ~ 5+480</dd></div>
|
||||
<div><dt>부품</dt><dd id="metaCount">—</dd></div>
|
||||
<div><dt>단위</dt><dd>m</dd></div>
|
||||
<div><dt>상태</dt><dd id="status" role="status">불러오는 중</dd></div>
|
||||
</dl>
|
||||
<div class="seg" role="group" aria-label="화면 테마">
|
||||
<button type="button" data-theme-choice="system" aria-pressed="true">시스템</button>
|
||||
<button type="button" data-theme-choice="light" aria-pressed="false">라이트</button>
|
||||
<button type="button" data-theme-choice="dark" aria-pressed="false">다크</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="shell">
|
||||
<nav class="rail" aria-label="모델 제어">
|
||||
<div class="btn-grid" role="group" aria-label="시점">
|
||||
<button type="button" class="primary" id="btnStart">종단 보기</button>
|
||||
<button type="button" id="btnAll">전체 맞추기</button>
|
||||
</div>
|
||||
|
||||
<h2>구간</h2>
|
||||
<div class="dual">
|
||||
<div class="track"></div>
|
||||
<input type="range" id="s0" min="4745" max="5480" step="5" value="4745" aria-label="시작 측점">
|
||||
<input type="range" id="s1" min="4745" max="5480" step="5" value="5480" aria-label="끝 측점">
|
||||
</div>
|
||||
<p class="sta" id="staText" aria-live="polite">4+745 ~ 5+480</p>
|
||||
<div class="btn-grid">
|
||||
<button type="button" id="btnWin">구간 맞추기</button>
|
||||
<button type="button" id="btnWinReset" class="quiet">구간 전체</button>
|
||||
</div>
|
||||
|
||||
<h2>지보패턴</h2>
|
||||
<div class="pats" id="pats"></div>
|
||||
|
||||
<h2>구성요소</h2>
|
||||
<div id="comps"></div>
|
||||
|
||||
<details class="disc" id="partDisc">
|
||||
<summary><span id="partSummary">부품 선택</span></summary>
|
||||
<select id="pickSel" aria-label="부품 목록에서 고르기">
|
||||
<option value="">선택 없음</option>
|
||||
</select>
|
||||
<dl class="spec" id="detailSpec" hidden></dl>
|
||||
<p class="empty" id="detailEmpty">부품을 클릭하거나 목록에서 고르면 속성이 표시됩니다.</p>
|
||||
<div class="btn-grid" style="margin-block-start:var(--prism-space-100)">
|
||||
<button type="button" id="btnFitSel">선택 맞추기</button>
|
||||
<button type="button" id="btnDesel" class="quiet">선택 해제</button>
|
||||
<button type="button" id="btnHideSel">선택 숨기기</button>
|
||||
<button type="button" id="btnIsoSel">선택만 보기</button>
|
||||
<button type="button" id="btnShowAll" class="quiet wide">모두 보기</button>
|
||||
</div>
|
||||
<label class="opt" style="margin-block-start:var(--prism-space-050)"><input type="checkbox" id="wire"> 와이어프레임으로 보기</label>
|
||||
</details>
|
||||
</nav>
|
||||
|
||||
<main id="main" class="stage">
|
||||
<div id="viewport" role="img" aria-label="대산2터널 본선 3D 모델, 측점 4+745부터 5+480까지. 드래그로 회전하고 클릭하면 부품이 선택됩니다. 키보드는 부품 선택 목록을 사용합니다."></div>
|
||||
<div id="tip" aria-hidden="true"></div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
import * as THREE from 'three';
|
||||
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
|
||||
const MODEL = '../model/tunnel_solids.glb';
|
||||
const META = '../model/tunnel_solids_meta.json';
|
||||
const S_MIN = 4745, S_MAX = 5480;
|
||||
const THEME_KEY = 'prism-viewer-theme';
|
||||
|
||||
// 부품 그룹 → 디자인 토큰 (색은 모두 토큰에서 읽는다, 리터럴 없음)
|
||||
const PART = {
|
||||
'라이닝': { token: '--prism-dv-1', order: 0 },
|
||||
'굴착면': { token: '--prism-text-subtle', order: 1, ghost: true },
|
||||
'노면(계획고)': { token: '--prism-dv-4', order: 2 },
|
||||
'기층·인버트(가정)': { token: '--prism-dv-2', order: 3 },
|
||||
'배수(측구)': { token: '--prism-dv-3', order: 4 },
|
||||
'공동구(가정)': { token: '--prism-dv-6', order: 5 },
|
||||
'시설한계(참조선)': { token: '--prism-text-accent', order: 6 }
|
||||
};
|
||||
const TOGGLES = [
|
||||
['굴착면', '굴착 여유대'], ['노면(계획고)', '노면 (계획고)'], ['기층·인버트(가정)', '기층·인버트 (가정)'],
|
||||
['배수(측구)', '배수 (측구)'], ['공동구(가정)', '공동구 (가정)'], ['시설한계(참조선)', '시설한계 50 mm 참조선']
|
||||
];
|
||||
const NOTE = {
|
||||
'노면(계획고)': '노면 포장 280 mm · 측점별 편경사 적용 (도면 계획고 기준)',
|
||||
'기층·인버트(가정)': '가정 형상 · 도면에 표현되지 않아 가정하여 생성',
|
||||
'배수(측구)': '측구 300×836 mm 및 관 r150 (단면 도면 기준)',
|
||||
'공동구(가정)': '가정 형상 · H/I 박스를 공동구로 가정',
|
||||
'시설한계(참조선)': '시설한계 50 mm 기준선 · 참조용 선 (솔리드 아님)',
|
||||
'굴착면': '굴착 여유대 R 7.402 m 기준 · 노면 위',
|
||||
'라이닝': '라이닝 R 6.982 / 7.282 m (지보패턴별 구간)'
|
||||
};
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const fmtSta = (m) => Math.floor(m / 1000) + '+' + String(Math.round(m % 1000)).padStart(3, '0');
|
||||
const tok = (name) => getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
||||
const statusEl = $('status'), tipEl = $('tip');
|
||||
|
||||
// ── 테마 (TC-1~TC-4): 시스템 기본, 사용자 재정의 영속 ──
|
||||
let scene = null, renderer = null;
|
||||
let currentChoice = 'system';
|
||||
try { currentChoice = localStorage.getItem(THEME_KEY) || 'system'; } catch (e) { /* 저장소 불가 시 시스템 값 */ }
|
||||
function setThemeAttr(choice) {
|
||||
const root = document.documentElement;
|
||||
if (choice === 'light' || choice === 'dark') root.setAttribute('data-theme', choice);
|
||||
else root.removeAttribute('data-theme');
|
||||
document.querySelectorAll('[data-theme-choice]').forEach((b) =>
|
||||
b.setAttribute('aria-pressed', String(b.dataset.themeChoice === choice)));
|
||||
}
|
||||
function applyTheme(choice, persist = true) {
|
||||
currentChoice = choice;
|
||||
setThemeAttr(choice);
|
||||
if (persist) { try { localStorage.setItem(THEME_KEY, choice); } catch (e) { /* 무시 */ } }
|
||||
if (scene) refreshColors();
|
||||
}
|
||||
setThemeAttr(currentChoice);
|
||||
document.querySelectorAll('[data-theme-choice]').forEach((b) =>
|
||||
b.addEventListener('click', () => applyTheme(b.dataset.themeChoice)));
|
||||
matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { if (scene) refreshColors(); });
|
||||
|
||||
// ── 3D 장면 ──
|
||||
const vp = $('viewport');
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
|
||||
renderer.setSize(vp.clientWidth, vp.clientHeight);
|
||||
renderer.domElement.setAttribute('aria-hidden', 'true');
|
||||
vp.appendChild(renderer.domElement);
|
||||
scene = new THREE.Scene();
|
||||
const camera = new THREE.PerspectiveCamera(30, vp.clientWidth / Math.max(vp.clientHeight, 1), 0.5, 200000);
|
||||
const controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
scene.add(new THREE.HemisphereLight(new THREE.Color(1, 1, 1), new THREE.Color(0.333, 0.376, 0.439), 1.0)); // 조명(장면 광원, UI 색 아님)
|
||||
const sun = new THREE.DirectionalLight(0xffffff, 0.9); sun.position.set(1, 2, 1); scene.add(sun);
|
||||
const fill = new THREE.DirectionalLight(0xffffff, 0.6); fill.position.set(-1, -0.5, -1.2); scene.add(fill);
|
||||
new ResizeObserver(() => {
|
||||
const w = vp.clientWidth, h = Math.max(vp.clientHeight, 1);
|
||||
renderer.setSize(w, h); camera.aspect = w / h; camera.updateProjectionMatrix();
|
||||
}).observe(vp);
|
||||
|
||||
const meshes = [];
|
||||
let META_BY_ID = {};
|
||||
const patternOn = {}, groupOn = {};
|
||||
let winLo = S_MIN, winHi = S_MAX;
|
||||
let selected = null, isolate = false;
|
||||
const hiddenSel = new Set();
|
||||
let world = null;
|
||||
|
||||
// 지보패턴별 색: 패턴 순번(측점 순)마다 dv 토큰을 쓰고, 7번째부터는 배경과 50% 혼합한 명도 차로 구분
|
||||
const patIdx = {};
|
||||
function patCss(p) { const k = patIdx[p] ?? 0; return 'color-mix(in srgb, var(--prism-dv-' + (k % 6 + 1) + ') ' + (k >= 6 ? 50 : 100) + '%, var(--prism-bg-canvas))'; }
|
||||
function patColor(p) {
|
||||
const k = patIdx[p] ?? 0;
|
||||
const c = new THREE.Color(tok('--prism-dv-' + (k % 6 + 1)));
|
||||
if (k >= 6) c.lerp(new THREE.Color(tok('--prism-bg-canvas')), 0.5);
|
||||
return c;
|
||||
}
|
||||
|
||||
function refreshColors() {
|
||||
scene.background = new THREE.Color(tok('--prism-bg-canvas'));
|
||||
for (const m of meshes) {
|
||||
const u = m.userData, p = PART[u.group];
|
||||
if (u.group === '라이닝' && u.pattern) m.material.color.copy(patColor(u.pattern));
|
||||
else m.material.color.set(tok(p ? p.token : '--prism-text-subtle'));
|
||||
}
|
||||
if (selected) selected.material.emissive.set(tok('--prism-text-heading'));
|
||||
}
|
||||
|
||||
function visibleOf(m) {
|
||||
const u = m.userData;
|
||||
if (hiddenSel.has(m)) return false;
|
||||
if (isolate && selected && m !== selected) return false;
|
||||
if (u.from != null && (u.to < winLo || u.from > winHi)) return false;
|
||||
if (u.group === '시설한계(참조선)' && (winLo > S_MIN || winHi < S_MAX)) return false; // 전체 길이 참조선: 전체 구간에서만
|
||||
if (u.pattern) { if (!patternOn[u.pattern]) return false; }
|
||||
if (u.group in groupOn && !groupOn[u.group]) return false;
|
||||
return true;
|
||||
}
|
||||
function applyVisibility() { for (const m of meshes) m.visible = visibleOf(m); }
|
||||
|
||||
function frame(center, dist, dir) {
|
||||
const d = (dir || new THREE.Vector3(0.6, 0.45, 0.9)).clone().normalize();
|
||||
controls.target.copy(center);
|
||||
camera.position.copy(center).addScaledVector(d, dist);
|
||||
camera.near = Math.max(dist / 3000, 0.05); camera.far = dist * 40;
|
||||
camera.updateProjectionMatrix();
|
||||
controls.update();
|
||||
}
|
||||
function boxOf(list) {
|
||||
const b = new THREE.Box3(); let any = false;
|
||||
for (const m of list) { b.union(new THREE.Box3().setFromObject(m)); any = true; }
|
||||
return any ? b : null;
|
||||
}
|
||||
function frameBox(b, pad) {
|
||||
const c = b.getCenter(new THREE.Vector3()), s = b.getSize(new THREE.Vector3());
|
||||
frame(c, Math.max(s.x, s.y, s.z) * (pad || 0.9) + 20);
|
||||
}
|
||||
function frameAll() { applyVisibility(); const b = boxOf(meshes.filter((m) => m.visible)); if (b) frameBox(b, 0.75); }
|
||||
function frameWindow() { applyVisibility(); const b = boxOf(meshes.filter((m) => m.visible)); if (b) frameBox(b, 0.9); }
|
||||
|
||||
// 종단 시점: 측점이 좌→우로 나열, 높이는 위
|
||||
function startView() {
|
||||
const first = meshes.find((m) => m.userData.group === '라이닝' && m.userData.from === S_MIN);
|
||||
const last = meshes.find((m) => m.userData.group === '라이닝' && m.userData.to === S_MAX);
|
||||
if (!first || !last) return frameAll();
|
||||
const c0 = new THREE.Box3().setFromObject(first).getCenter(new THREE.Vector3());
|
||||
const c1 = new THREE.Box3().setFromObject(last).getCenter(new THREE.Vector3());
|
||||
const u = new THREE.Vector3(c1.x - c0.x, 0, c1.z - c0.z);
|
||||
const L = u.length(); u.normalize();
|
||||
const mid = c0.clone().add(c1).multiplyScalar(0.5);
|
||||
const w = new THREE.Vector3(u.z, 0, -u.x); // 카메라 시선 방향 (측점 u가 화면 우측)
|
||||
controls.target.copy(mid);
|
||||
camera.up.set(0, 1, 0);
|
||||
camera.position.copy(mid).addScaledVector(w, -L * 2.1).add(new THREE.Vector3(0, L * 0.02, 0));
|
||||
camera.near = 1; camera.far = L * 40; camera.updateProjectionMatrix();
|
||||
controls.update();
|
||||
}
|
||||
|
||||
// 부품 선택 개폐 상태 저장 (DC-3)
|
||||
const DISC_KEY = 'prism-viewer-part-open';
|
||||
try { $('partDisc').open = localStorage.getItem(DISC_KEY) === '1'; } catch (e) { /* 무시 */ }
|
||||
$('partDisc').addEventListener('toggle', () => { try { localStorage.setItem(DISC_KEY, $('partDisc').open ? '1' : '0'); } catch (e) { /* 무시 */ } });
|
||||
|
||||
// ── 부품 상세 (명세표) ──
|
||||
function specRows(u) {
|
||||
return [
|
||||
['구분', u.group || '—'],
|
||||
['지보패턴', u.pattern || '—'],
|
||||
['측점', u.from != null ? fmtSta(u.from) + ' ~ ' + fmtSta(u.to) + ' (' + Math.round(u.to - u.from) + ' m)' : '—'],
|
||||
['부피', u.volume != null ? Number(u.volume).toFixed(2) + ' m³' : '—'],
|
||||
['비고', NOTE[u.group] || '—'],
|
||||
['부품 ID', u.id || '—']
|
||||
];
|
||||
}
|
||||
function fillSpec(dl, rows) {
|
||||
dl.replaceChildren();
|
||||
for (const [k, v] of rows) {
|
||||
const dt = document.createElement('dt'); dt.textContent = k;
|
||||
const dd = document.createElement('dd'); dd.textContent = v;
|
||||
dl.append(dt, dd);
|
||||
}
|
||||
}
|
||||
function renderDetail(u) {
|
||||
const dl = $('detailSpec'), empty = $('detailEmpty');
|
||||
if (!u) { dl.hidden = true; empty.hidden = false; dl.replaceChildren(); return; }
|
||||
fillSpec(dl, specRows(u)); dl.hidden = false; empty.hidden = true;
|
||||
}
|
||||
|
||||
function setSelected(m) {
|
||||
if (selected) selected.material.emissive.setRGB(0, 0, 0);
|
||||
selected = m;
|
||||
if (m) m.material.emissive.set(tok('--prism-text-heading'));
|
||||
$('pickSel').value = m ? String(m.userData.idx) : '';
|
||||
$('partSummary').textContent = m ? '부품 선택 · ' + m.userData.id : '부품 선택 · 선택 없음';
|
||||
applyVisibility();
|
||||
renderDetail(m ? m.userData : null);
|
||||
}
|
||||
|
||||
function showTip(u, x, y) {
|
||||
tipEl.replaceChildren();
|
||||
const t = document.createElement('p'); t.className = 'tip-title';
|
||||
t.textContent = u.group + (u.pattern ? ' · 지보패턴 ' + u.pattern : '');
|
||||
const dl = document.createElement('dl'); dl.className = 'spec';
|
||||
fillSpec(dl, specRows(u).filter((r) => r[0] !== '구분' && r[0] !== '지보패턴'));
|
||||
tipEl.append(t, dl);
|
||||
tipEl.style.display = 'block';
|
||||
const gap = 14;
|
||||
let left = x + gap, top = y + gap;
|
||||
if (left + tipEl.offsetWidth > innerWidth) left = x - tipEl.offsetWidth - gap;
|
||||
if (top + tipEl.offsetHeight > innerHeight) top = y - tipEl.offsetHeight - gap;
|
||||
tipEl.style.left = Math.max(8, left) + 'px';
|
||||
tipEl.style.top = Math.max(8, top) + 'px';
|
||||
}
|
||||
function hideTip() { tipEl.style.display = 'none'; }
|
||||
|
||||
Promise.all([
|
||||
fetch(META).then((r) => r.json()),
|
||||
new Promise((res, rej) => new GLTFLoader().load(MODEL, res, undefined, rej))
|
||||
]).then(([meta, gltf]) => {
|
||||
for (const c of meta.components) META_BY_ID[c.id] = c;
|
||||
world = new THREE.Group();
|
||||
world.rotation.x = -Math.PI / 2; // 모델은 Z-up, 화면은 Y-up
|
||||
world.add(gltf.scene);
|
||||
scene.add(world);
|
||||
gltf.scene.traverse((o) => {
|
||||
if (!o.isMesh) return;
|
||||
const c = META_BY_ID[o.name] || {};
|
||||
o.userData = { id: o.name, group: c.group || '', pattern: c.pattern || null,
|
||||
from: c.from_sta ?? null, to: c.to_sta ?? null, volume: c.volume_m3 ?? null,
|
||||
idx: meshes.length };
|
||||
o.geometry = o.geometry.toNonIndexed();
|
||||
o.geometry.computeVertexNormals();
|
||||
const p = PART[o.userData.group];
|
||||
const mat = new THREE.MeshStandardMaterial({ roughness: 0.8, metalness: 0.0, side: THREE.DoubleSide, flatShading: true });
|
||||
if (p && p.ghost) { mat.transparent = true; mat.opacity = 0.28; mat.depthWrite = false; }
|
||||
o.material = mat;
|
||||
meshes.push(o);
|
||||
});
|
||||
world.updateMatrixWorld(true);
|
||||
refreshColors();
|
||||
|
||||
// 지보패턴 체크박스
|
||||
const firstFrom = {};
|
||||
for (const c of meta.components) if (c.pattern && c.from_sta != null) firstFrom[c.pattern] = Math.min(firstFrom[c.pattern] ?? Infinity, c.from_sta);
|
||||
const pats = Object.keys(firstFrom).sort((a, b) => firstFrom[a] - firstFrom[b]);
|
||||
pats.forEach((p, i) => { patIdx[p] = i; });
|
||||
refreshColors();
|
||||
for (const p of pats) {
|
||||
patternOn[p] = true;
|
||||
const lab = document.createElement('label'); lab.className = 'opt';
|
||||
const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = true;
|
||||
cb.style.accentColor = patCss(p);
|
||||
cb.addEventListener('change', () => { patternOn[p] = cb.checked; applyVisibility(); });
|
||||
const span = document.createElement('span'); span.textContent = p;
|
||||
lab.append(cb, span); $('pats').appendChild(lab);
|
||||
}
|
||||
|
||||
// 구성요소 체크박스 (색 견본 = 부품 토큰)
|
||||
for (const [key, text] of TOGGLES) {
|
||||
groupOn[key] = true;
|
||||
const lab = document.createElement('label'); lab.className = 'opt';
|
||||
const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = true;
|
||||
cb.addEventListener('change', () => { groupOn[key] = cb.checked; applyVisibility(); });
|
||||
const sw = document.createElement('span'); sw.className = 'sw' + (PART[key].ghost ? ' ghost' : '');
|
||||
sw.setAttribute('aria-hidden', 'true'); sw.style.background = 'var(' + PART[key].token + ')';
|
||||
const span = document.createElement('span'); span.textContent = text;
|
||||
lab.append(cb, sw, span); $('comps').appendChild(lab);
|
||||
}
|
||||
|
||||
// 키보드용 부품 목록 (그룹 → 측점 순)
|
||||
const order = [...meshes].sort((a, b) =>
|
||||
(PART[a.userData.group]?.order ?? 9) - (PART[b.userData.group]?.order ?? 9) ||
|
||||
(a.userData.from ?? 0) - (b.userData.from ?? 0) || a.userData.id.localeCompare(b.userData.id));
|
||||
for (const m of order) {
|
||||
const u = m.userData;
|
||||
const o = document.createElement('option');
|
||||
o.value = String(u.idx);
|
||||
o.textContent = u.group + (u.pattern ? ' · ' + u.pattern : '') +
|
||||
(u.from != null ? ' · ' + fmtSta(u.from) + '~' + fmtSta(u.to) : '') + ' · ' + u.id;
|
||||
$('pickSel').appendChild(o);
|
||||
}
|
||||
|
||||
startView();
|
||||
$('metaCount').textContent = String(meshes.length);
|
||||
statusEl.textContent = '로드 완료';
|
||||
}).catch((e) => { statusEl.textContent = '로드 실패: HTTP 서버에서 여세요 (' + e.message + ')'; });
|
||||
|
||||
// ── 구간 슬라이더 (두 핸들) ──
|
||||
function syncWin(from) {
|
||||
let a = Number($('s0').value), b = Number($('s1').value);
|
||||
if (a > b) { if (from === 0) { b = a; $('s1').value = b; } else { a = b; $('s0').value = a; } }
|
||||
winLo = a; winHi = b;
|
||||
$('staText').textContent = fmtSta(a) + ' ~ ' + fmtSta(b);
|
||||
$('s0').setAttribute('aria-valuetext', fmtSta(a));
|
||||
$('s1').setAttribute('aria-valuetext', fmtSta(b));
|
||||
applyVisibility();
|
||||
}
|
||||
$('s0').addEventListener('input', () => syncWin(0));
|
||||
$('s1').addEventListener('input', () => syncWin(1));
|
||||
$('btnWin').addEventListener('click', () => frameWindow());
|
||||
$('btnWinReset').addEventListener('click', () => { $('s0').value = S_MIN; $('s1').value = S_MAX; syncWin(0); frameAll(); });
|
||||
$('btnStart').addEventListener('click', () => startView());
|
||||
$('btnAll').addEventListener('click', () => frameAll());
|
||||
|
||||
// ── 선택 도구 ──
|
||||
$('btnFitSel').addEventListener('click', () => { if (selected) frameBox(new THREE.Box3().setFromObject(selected), 0.9); });
|
||||
$('btnDesel').addEventListener('click', () => { setSelected(null); hideTip(); });
|
||||
$('btnHideSel').addEventListener('click', () => { if (selected) { hiddenSel.add(selected); setSelected(null); hideTip(); } });
|
||||
$('btnIsoSel').addEventListener('click', () => { if (selected) { isolate = true; applyVisibility(); } });
|
||||
$('btnShowAll').addEventListener('click', () => { hiddenSel.clear(); isolate = false; applyVisibility(); });
|
||||
$('wire').addEventListener('change', () => { for (const m of meshes) m.material.wireframe = $('wire').checked; });
|
||||
$('pickSel').addEventListener('change', () => {
|
||||
const v = $('pickSel').value;
|
||||
setSelected(v === '' ? null : meshes[Number(v)]);
|
||||
});
|
||||
addEventListener('keydown', (e) => { if (e.key === 'Escape') { setSelected(null); hideTip(); } });
|
||||
|
||||
// ── 포인터 선택 ──
|
||||
const ray = new THREE.Raycaster(); const ptr = new THREE.Vector2();
|
||||
let downAt = null;
|
||||
renderer.domElement.addEventListener('pointerdown', (e) => { downAt = [e.clientX, e.clientY]; });
|
||||
renderer.domElement.addEventListener('pointerup', (e) => {
|
||||
if (!downAt || Math.hypot(e.clientX - downAt[0], e.clientY - downAt[1]) > 4) return;
|
||||
const r = renderer.domElement.getBoundingClientRect();
|
||||
ptr.set(((e.clientX - r.left) / r.width) * 2 - 1, -((e.clientY - r.top) / r.height) * 2 + 1);
|
||||
ray.setFromCamera(ptr, camera);
|
||||
const hit = ray.intersectObjects(meshes.filter((m) => m.visible), false)[0];
|
||||
if (!hit) { setSelected(null); hideTip(); return; }
|
||||
setSelected(hit.object);
|
||||
showTip(hit.object.userData, e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
renderer.setAnimationLoop(() => { controls.update(); renderer.render(scene, camera); });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user