Files
Pier_v1/Code/rebar_pipeline/README.md
T
ahnyongjooandClaude Sonnet 5.5 964a841de1 chore(rebar): Pier_T_rebar 작업 폴더 초기 구성 및 외부 의존 파일 통합
- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환
- Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용
- pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관)
- worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md)
- D:\Rhino-MCP 경로 참조를 새 위치로 치환

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 11:20:18 +09:00

192 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 철근 생성·검토·재료표 파이프라인 (대호지교 P1 두부보 작업에서 검증, 2026-10-02)
나중에 "도면(DXF) → 라이노 철근 생성 → 검토 → 재료표 DXF 출력" 프로그램으로 만들기 위한 방법 정리.
모든 Rhino 코드는 rhino_mcp `execute_rhino_code` (IronPython 2.7) 기준.
---
## 0. 환경 제약 (반드시 지킬 것)
- f-string 금지, walrus 금지, 타입힌트 금지.
- **exec 스코프 문제**: 함수 안에서 모듈 전역 변수를 못 본다. 리스트 컴프리헨션·lambda 안에서 바깥 변수 참조 시 `name 'x' is not defined` 에러.
- 해결: 루프는 `while/for`로 풀어 쓰기, 함수가 필요하면 기본인자로 바인딩 `def L(x, ents=ents):`.
- 출력은 마지막에 `result = "..."; print(result)`.
- 무거운 계산(`AreaMassProperties.Compute` 다수 호출)은 MCP 소켓 타임아웃 → 마크별로 나눠 호출, 면적 대신 isocurve 길이 사용.
- `sc.sticky[...]`에 중간 결과를 저장해서 다음 호출에서 이어 쓰기.
- 이 PC에는 Python/ezdxf 없음 → DXF 파싱은 bash `tr -d '\r' | awk`, DXF 쓰기는 IronPython으로 UTF-8 저장 후 `iconv -t CP949`.
- 라이노 오브젝트 삭제는 사용자 확인 후에만.
---
## 1. 도면(DXF) 읽기
DXF는 AC1021(UTF-8 텍스트), `$DWGCODEPAGE=ANSI_949`. group code/value가 2줄씩 반복.
- `awk/txt.awk` : TEXT/MTEXT/ATTRIB → `x y 타입 문자열` (좌표 정렬하면 표가 행 단위로 읽힘)
- `awk/geo.awk` : LINE / LWPOLYLINE / ARC → 선형 추출 (레이어 포함)
- `awk/ins.awk` : INSERT / CIRCLE 등 → 블록 삽입점 (철근 단면 점 = 블록 `CSBSEC1`)
```bash
tr -d '\r' < "철근재료표 및 상세(...).dxf" | awk -f txt.awk | sort -t$'\t' -k2,2nr -k1,1n > mat.tsv
tr -d '\r' < "코핑구조도(...).dxf" | awk -f geo.awk > cop_geo.tsv
```
### 1-1. 재료표 해석 (같은 y = 같은 행)
열 x 경계(원본): 번호|형식|직경|개수|A|B|C|D|E|R|X1|X2|J|길이|총길이|단위중량|총중량|총중량(할증)
- `"` = 위 행과 같은 직경.
- 1000mm 이상은 m(소수 3자리), 미만은 mm 정수로 표기.
- 단위중량: H16 1.560, H19 2.250, H22 3.040, H25 3.980, H29 5.040 (kg/m).
- 할증: H22 이상 6%, H19 이하 3%. 별도로 "소계(겹이음 할증 6.80%)" 행 = 소계중량×1.068.
- **형식 V(⊓뚜껑+⊔바닥 박스) 길이 규칙: L = 2A + 2B + 2C** (A=폭, 1250/1005 두 종류면 평균; B=박스 전체높이(바닥 다리); C=뚜껑 다리 510=겹침). 재료표 L은 평균, 총길이=Σ.
- **형식 VII(C8/C10)**: L = a+b+c+d, X1/X2 = 꺾임 사잇각.
### 1-2. 조립도/평면도에서 형상·위치 읽기
- 레이어 `CS-RBAR-MAJR` 선 = 철근 중심선. `CS-CONC-MAJR` = 콘크리트 외곽. `CS-CNTL` = 중심선.
- 텍스트 "주철근 조립도(정면도) ①CYCLE / ②CYCLE" 근처 ATTRIB(마크명 + "H25 @ 250")로 어떤 마크가 어느 CYCLE인지 판단.
- 조립도 좌표 → 코핑 로컬 s: `s = x - (코핑 중심 x)`; 코핑 중심 = 길이 23960 주철근 양 끝 중점 등으로 구함.
- 평면도에서 폭방향 위치: 수평 RBAR 선들의 y 값 → `w = y - 중심선y`. 치수 tick(CZDIMA1 블록) 간격(120, 130, 24@125 …)으로 피복·간격 확인.
- **검증 요령**: 도면 선 길이가 재료표 치수와 정확히 같으면(예: 4167.1, 3158.8) 그 도면 선이 철근 중심선임을 확신할 수 있다.
---
## 2. 코핑 로컬 좌표계 & 단면 외곽
```python
ang = math.radians(19.105) # 코핑 길이방향 (수평 edge 방향 히스토그램으로 구함)
u = Vector3d(cos, sin, 0) # 길이방향 s
wv = Vector3d(-sin, cos, 0) # 폭방향 w
c = coping.GetBoundingBox(True).Center # 원점
```
- 길이방향 각도: Brep.Edges 중 수평(|dz|<1)·길이>1000 edge들의 atan2 각도 히스토그램.
- 단면 외곽: `Intersection.BrepPlane(brep, Plane(c, u, Z))` → `DuplicateSegments()` → 각 세그먼트를 (s, z)로 출력.
- 뷰 정렬(좌표 안 바꾸고 보기만): `vp.SetCameraLocations(target, target - dir*1e5); vp.CameraUp = Z` (Left: dir=u, Front: dir=u를 90° 회전), `ChangeToParallelProjection(True)`.
---
## 3. 철근 중심선 생성 (피복 = 철근 중심 기준 120)
1. 단면 외곽의 각 면을 **안쪽으로 120 오프셋한 직선**들로 표현 (점 + 방향 + 안쪽 법선).
2. 인접 오프셋선끼리 교점 → 꺾임점. 2D (s,z)에서 직접 풀면 됨.
3. 도면에 고정 길이 구간이 있으면(예: a=630 수직다리, d=2331 끝 연장) 그 길이만큼 연장해 끝점 결정.
4. 3D 변환: `P = c + u*s + wv*w + Z*(z - c.Z)`; 대칭 마크는 s 부호만 반전.
5. 폭방향 위치(w)는 도면 평면도 기준. 1CYCLE: ±1630, ±1375 … ±125 (14본) / 2CYCLE: ±1500 … 0 (13본).
6. 굽힘: KDS 24 14 21 표 4.5-1 최소 내면지름(이형 D16이하 2db, D19이상 3.5db → 내면 반지름 1db/1.75db) 이상의 내면 R + 철근반지름 = 중심선 R. 적용할 R값은 사용자 결정 [미확정]. (기존 모델의 R=3db/4db는 과거 값)
```python
pc = Polyline(pts).ToPolylineCurve()
fc = Curve.CreateFilletCornersCurve(pc, Rc, tol, angtol) # 모든 내부 모서리 한 번에
brep = Brep.CreatePipe(fc, r, False, PipeCapMode.NONE, False, tol, angtol)[0]
```
검증: `1/fc.CurvatureAt(t).Length` 최소값 == Rc.
7. 블록에 넣을 때: 인스턴스 Xform 역변환 후 `InstanceDefinitions.ModifyGeometry(idx, geoms, attrs)` (**3인자 형태, 원본 attrs Duplicate** — 색상 깨짐 방지). 수정 전 기존 형상은 `<마크>_backup_YYYYMMDD` 블록정의로 보관.
### 색상 규칙 (직경별, ACI)
H10/H13=74, H16=5, H19=210, **H22 이상 전부 (104,0,25)**.
- 얕은 구조(문서인스턴스→leaf): leaf `ColorFromLayer`, 마크 레이어 색.
- 깊은 구조(C12~C18: mark→station→box→leaf): **station·box·leaf 3단계 모두 `ColorFromParent`**. 하나라도 ColorFromLayer면 엉뚱한 레이어색(코핑 황토색)으로 보임.
---
## 4. 검토(감사) 방법
### 4-1. 파이프 Brep에서 중심선 복원 (빠름)
각 face에서 isocurve 한 방향이 닫힌 원(길이 2πr)이면 파이프 면 → 다른 방향 isocurve를 원주 0과 0.5 위치에서 2개 뽑아 평균:
- 중심선 길이 = (len(iso@0) + len(iso@0.5)) / 2 (굽힘 토러스에서도 정확)
- 직선 면이면 양 끝점 평균 = 중심선 선분.
- 선분들을 끝점 근접으로 체인 연결 → 인접 선분 `Intersection.LineLine`으로 가상 교점 → **재료표 방식 치수(교점 간 거리)**와 사잇각.
- 막힌(캡) 파이프: 평면 face 중심 = 열린 끝점. 열린 파이프: `DuplicateNakedEdgeCurves` 원 중심.
### 4-2. 피복
- **철근 표면을 `BrepFace.PointAt` 격자(60×24)로 샘플 → `coping.ClosestPoint` 최소거리 + r = 중심 피복.**
- FastRenderMesh 정점 사용 금지(엉터리 값 91~104 나옴).
- 수직 레이캐스트(`Intersection.RayShoot`)는 상·하면까지 거리 참고용. 경사면 근처는 ClosestPoint 사용.
- 상단 직선 철근: "그 철근이 지나는 구간의 가장 낮은 상면"에서 120 (C26 교훈).
### 4-3. 개수
- 단순 마크: 인스턴스 정의 leaf 개수.
- 박스 마크: mark idef → station 인스턴스 → box 인스턴스 개수(인스턴스 수로 셈, 정의 수 아님).
### 4-4. 박스(V형) 측정
box 정의의 조각 중 높이가 큰 2개 = 뚜껑/바닥(아래쪽이 높은 것이 뚜껑). 나머지 = 연장 필러.
- A = 뚜껑 양 끝점 거리, 뚜껑top = bbox.Max.Z − r, 바닥bot = bbox.Min.Z + r
- 필러가 뚜껑 끝에 닿으면 뚜껑 연장, 바닥 끝에 닿으면 바닥 연장.
- C = 뚜껑top − 뚜껑끝, B = 바닥끝 − 바닥bot, 겹침 = 바닥끝 − 뚜껑끝 (510 미만이면 결함).
- 재료표 비교: 재료표 A·높이 = 모델 + 47.6 (= 2×(주철근 r + 스터럽 r)) → 도면은 스터럽이 주철근 바깥을 감쌈.
---
## 5. 재료표 DXF 출력
- 형식: **R12 (AC1009)**, `$DWGCODEPAGE=ANSI_949`, TABLES에 LTYPE CONTINUOUS / LAYER / STYLE(KOR, malgun.ttf), ENTITIES에 LINE, TEXT(72=1, 73=2 가운데정렬 + 11/21 정렬점).
- 원본 표 규격: 열 경계 상대 x = 0,1200,2400,3600,4800,6800,8800,10800,12800,14800,16800,18800,20800,22800,24800,26400,28000,29600,31200 (+비고 열 ~40000), 헤더 960(2단), 행 480, 글자 200, 제목 560.
- 행 순서: [H29 그룹][소계][소계(겹이음 6.80%)] → H25 → H22 → H19 → H16 … → 총계, 총계(겹이음).
- 소계 행은 세로선을 왼쪽 끝 + 길이 열 이후만 그림(병합셀 흉내).
- 계산: 중량(t) = 총길이 × 단위중량 / 1000, 할증 = ×1.06 또는 ×1.03, 겹이음 = ×1.068. 총계는 반올림된 소계들의 합(원본과 동일 방식).
- 저장: IronPython `System.IO.File.WriteAllText(p, txt.replace("\n","\r\n"), UTF8Encoding(False))` → bash `iconv -f UTF-8 -t CP949`.
- 검증: `RhinoDoc.CreateHeadless(None).Import(path)`로 별도 문서에 불러와 텍스트/선 개수·한글 확인 후 Dispose (루프 변수 None 처리 필수).
- 파일명 규칙: `YYMMDD_철근재료표 작업중.dxf` (도면 폴더에 저장).
---
## 5-2. 측면철근/캡타이 (C19~C25, 2026-10-02 검증)
- **배치 근거**: 단면 A-A/B-B/C-C의 철근 점(블록 `CSBSEC1`) y값 = 측면철근 높이(200 간격), x값 = 폭 위치. 정면도 점 = 폭방향으로 가로지르는 철근(C25)의 (s, z). 정면도 점 개수 합 = 재료표 수량으로 검증.
- **도면→모델 좌표**: 정면도 `s = x − (코핑 중심 x)`, `z = 7738 + (y − 기둥위 하면 y)`. 방향 반전 여부는 비대칭 마크(C12 = s 최소 쪽)로 판단.
- **VI 닫힌 고리**: 높이별로 120 오프셋 경계(헌치 대각/끝면) + WRAP(주철근 r + 고리 r, 바깥 감싸기) → 재료표 A와 정확히 일치. 닫힌 폴리라인은 **변 중간에서 시작**해야 4모서리 모두 필렛됨. 띠철근 R=2db.
- **VIII 갈고리 직선**: 135° 갈고리, 중심 R=60 (호 길이 = 재료표 b 141), 연장 c=100, L=A+2(b+c). A = 고리 A − 9. **갈고리는 수평면 안에서, 폭 바깥쪽(w 부호 방향)으로** (사용자 지시 2026-10-02). 위·아래로 꺾지 않음 (아래는 헌치 관통, 위는 잘못된 방향). 안쪽으로 꺾으면 중앙 ±106 철근끼리 갈고리가 겹침.
- **III ⊓ (C25)**: 윗변 A=3260 폭방향(w ±1630), 다리 300 아래. 점 위치가 하면에 가까우면 다리 관통 → 생성 시 `(z−300) − 하면z < 120` 자동 검출해서 보고.
- 생성 방식: 마크별 InstanceDefinition 1개(월드좌표 형상, 기준점 원점) + Identity 인스턴스 1개, leaf ColorFromLayer, 레이어 `..._1CYCLE::C{n}_H16` 파랑(0,0,255).
- 피복 검증: 긴 철근 전체를 Brep.ClosestPoint로 샘플링하면 MCP 타임아웃 → 대표 지점 몇 개를 Brep.ClosestPoint로 재고, 관통 여부는 Edge 점 `IsPointInside`로.
- 화면 격리 보기: 다른 레이어 IsVisible=False (인덱스 목록 sticky에 저장) → 파일 저장 전 복구 → 저장 → 다시 숨김.
---
## 5-3. 간섭 / 튀어나옴 / 피복 일괄 체크 (2026-10-02 검증, 전체 26마크 ~30초)
1. **중심선 선분 추출**: 각 파이프 면에서 원주 0 위치 isocurve를 `ToPolyline(0,0,0.05,0,0,0.3,0,3000,True)`로 적응 분할 → 각 정점의 곡선 매개변수 t에서 `face.PointAt(t, v0)`와 `PointAt(t, v0.5)`의 중점 = 중심점. 중첩 블록은 Xform을 곱해 내려가며 처리, 같은 leaf 형상은 캐시.
2. **간섭**: 마크별 `RTree`(선분 bbox를 r만큼 키움) → 마크 bbox가 겹치는 쌍만 `RTree.SearchOverlaps(treeA, treeB, 0, System.EventHandler[RTreeEventArgs](cb))` (`e.Id`, `e.IdB`) → `Intersection.LineLine(..., finite=True)`, 실패(평행)면 `Line.MinimumDistanceTo` → 관입 = rA + rB − 거리 > 0.5 이면 기록(철근쌍별 최대).
3. **피복/튀어나옴**: 코핑이 폭방향 압출 형상이므로, 점을 (s,w,z)로 바꿔 2D 단면 다각형 내부 판정 + 변까지 거리, 측면은 1750−|w|. 중심피복 < r 이면 튀어나옴. (Brep.ClosestPoint 대량 호출은 타임아웃, 메시 ClosestPoint는 값이 틀어짐)
4. **출력**: CSV(UTF-8 BOM, 엑셀 한글) + 요약 md + 라이노 체크 레이어(TextDot, PointCloud) — 모델 자체는 건드리지 않음.
---
## 5-4. CTC 검사 · 박스 철근 보정 · 갈고리 반전 · 재료표 정렬 (2026-10-06 검증)
**CTC(철근 중심 간격) 검사** — `rhino/07_ctc_extract.py`
- 마크별 리프 Brep 중심점을 코핑 로컬 (s,t,z)로 뽑아 JSON → 고유값(허용오차 1~2mm) 정렬 → 차이를 도면 `@` 표기와 대조.
- 도면 간격 표기는 `코핑구조도` DXF의 `CS-DIMT` TEXT (`24@125=3.000`, `11@250=2.750`, 평면 스터럽 `7@300|14@150|8@300|16@150|19@300|…`, `38@600=22.800`).
- 주의: 같은 칸의 뚜껑/바닥 조각은 s가 다를 수 있다. 클러스터의 **첫 값만** 쓰면 가짜 어긋남이 생긴다(실제로 한 번 오판함) → 칸 안의 모든 리프 s의 min/max를 같이 볼 것.
- 이 값들은 철근 마크 레이어가 꺼져 있으면 `doc.Objects` 기본 열거에서 빠진다 → `ObjectEnumeratorSettings.HiddenObjects=True`.
**박스(⊓+⊔) 바닥 조각 보정** — `rhino/05_realign_base_pieces.py`
- 증상: 피복 보정 때 바닥 조각을 헌치 법선 방향으로 옮겨서 뚜껑과 s 평면이 1~3mm 어긋남 (C12/C18 2.68, C14/C16 1.95).
- 보정: 어긋남 −dev 만큼 s 이동 + 헌치 경사 접선 방향으로 dz = (경사)·ds 이동(수직 피복 유지) → 뚜껑 윗변 중심 z에서 `Brep.Trim` 으로 상단 재절단(겹침 510).
- 박스 idef는 좌우 대칭 인스턴스가 공유하므로 idef 하나만 고치면 4개 박스에 동시 적용됨(검증은 모든 인스턴스로).
- `ModifyGeometry(idx, geoms, attrs)` 3-인자 + `attrs=ob.Attributes.Duplicate()` 로 ColorFromParent 보존. 백업 idef `<이름>_bk1006b`.
**갈고리 방향 반전** — `rhino/06_flip_hooks.py`
- 철근 축을 지나는 s-수직 평면으로 자른 단면 원 중심 = 축 위의 점 → `Transform.Mirror(축점, 폭방향 법선)` + `Brep.Flip()`. 길이 불변.
- 선택은 centroid t (갈고리 때문에 축에서 ~2mm 벗어남) 대역으로 하고, **개수가 예상과 다르면 수정하지 않고 보고**.
**재료표 DXF 텍스트 정렬** — `dxf/leftalign.ps1`
- R12/CP949 DXF에서 표 칸 왼쪽 경계(세로선) + 200 위치로 TEXT 왼쪽 정렬(72=0, 73=2). `powershell -File leftalign.ps1 -In 원본.dxf -Out 새파일.dxf -Pad 200`.
- 원본 재료표는 가운데(72=1) 242개 / 오른쪽(72=2) 58개 정렬 — 이 파이프라인의 표는 왼쪽 정렬 규칙을 쓴다(사용자 지시).
**화면/레이어 함정**
- 블록 안쪽 리프가 **다른 레이어**(예: `P1_두부(코핑)`)에 있으면, 마크 레이어를 켜도 그 레이어가 꺼져 있으면 안 그려진다. C12~C18 스터럽 리프가 코핑 레이어에 있음 → 철근만 보려면 코핑 **레이어는 켜 두고 솔리드/보조선만 객체 단위로 숨긴다**.
- 화면 격리 후 저장할 때는: 현재 표시 상태 저장 → 원래 상태 복구 + 숨긴 객체 Show → `doc.Save()` → 격리 상태 재적용. (저장은 127MB라 MCP 소켓 타임아웃이 나지만 Rhino 안에서는 계속 진행됨 → 결과 파일 생길 때까지 until-loop로 대기.)
- Left 뷰 정렬: `SetCameraLocations(target, target - dir*60000)`, dir=(cos a, sin a, 0), `CameraUp=Z`, 그 뒤 `ZoomBoundingBox`.
---
## 6. 프로그램화 시 모듈 구성 제안
1. `dxf_reader` – txt/geo/ins 추출, 재료표 행 파서, CYCLE별 마크 목록, 조립도 철근 polyline.
2. `section` – 콘크리트 Brep → 로컬 프레임(u,w,원점) → 단면 외곽 (s,z) 세그먼트.
3. `rebar_builder` – 형식(I~VIII)별 중심선 생성기(오프셋선 교점 + 고정길이), 폭방향 배열, KDS R 필렛, 파이프, 블록/레이어/색.
4. `auditor` – isocurve 중심선 복원, 치수/각도/개수/피복/겹침 비교 → 차이 리포트.
5. `table_writer` – R12 CP949 재료표 DXF (원본 규격 + 비고 열).
## 5-5. 간섭 재검사 스크립트 (2026-10-06) — `rhino/08_clash_check.py`
- 읽기 전용. 철근별 단면 원 중심 → 중심선 선분 → RTree → 선분 최단거리, 관입 = rA+rB−거리 > 0.5. 박스(뚜껑·바닥·연장 조각) 내부와 같은 철근은 제외, P1 레이어만 (P2/P10/P12 숨김 레이어 복사본 제외).
- 함정: ① `BoundingBox(p0, p1)` 는 점이 min→max 순서가 아니면 무효 상자 → 큰 교차가 통째로 누락됨. `BoundingBox([p0, p1])` 사용. ② 숨김 레이어 객체는 `ObjectEnumeratorSettings.HiddenObjects=True` 로 열거해야 함. ③ NURBS 파라미터 0.5 지점은 반대편 점이 아님 → 단면 원 `TryGetCircle` 중심 사용. ④ 실행이 길어 소켓 타임아웃이 나도 Rhino 안에서는 계속 돈다 → 결과 파일 갱신을 until-루프로 대기.