feat: read concrete-lining drawings 04-004..009 and link lining spec to model (Task 6)
Extracted per-sheet pattern groups (입출구부 50 m / 온도미고려), note-5 scope, fck 27 MPa, SD400, aggregate 25 mm, rebar label counts and printed radii. Model keeps R1 = 6,982 mm; lining sheets print 6.892 m -> [CONFLICT DETECTED] for user check. Sheet-to-station and pattern-name mapping left [미확정]. Viewer shows the lining spec on click. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A5Cun3RHQKna9qxE6ybZ4A
This commit is contained in:
1 parent
6c7e555c25
commit
fe6fe26655
8 files changed
+9593
-4
No files matched your search
@@ -191,7 +191,14 @@ meta = {
|
||||
"pattern_status": "PLACEHOLDER: 2 m 임의 배정. 실제 측점 구간 아님 (사용자 결정 2026-10-21). data/pattern_zones_placeholder.csv 를 수정 후 재빌드",
|
||||
"patterns": [{"from": a, "to": b, "pattern": pat} for a, b, pat, _ in ZONES],
|
||||
"open": ["4+700~4+745 and 5+480~5+500 excluded ([미확정])", "pattern zones (03-003~008) pending Task 5",
|
||||
"lining details (04-004~009) pending Task 6"]
|
||||
"lining reference recorded (meta.lining_reference, 04-004~009); sheet-to-station mapping [미확정]",
|
||||
"[CONFLICT DETECTED] lining sheets print R1=6.892 m, standard section R1=6,982 mm (model keeps 6.982) - user check"]
|
||||
}
|
||||
_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,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,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,61 @@
|
||||
#!/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} ({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. 충돌·미확정",
|
||||
"- [CONFLICT DETECTED] 도면 R1 6.892 m 와 표준단면 R1 6.982 m 가 다르다. 골격은 6.982 m 를 유지하고 확인을 요청한다.",
|
||||
"- [미확정] 시트별 패턴군이 어느 측점 구간인지 (도면에 측점 없음)",
|
||||
"- [미확정] 라이닝 패턴군(A~C-2, D-1~2, E-1~2, WL-1~4, RP-1~2, P-2~3)과 굴착 순서도 패턴명(NP-4AD-1-D-2 등)의 대응 관계",
|
||||
"- 재료표 수량(총 중량)은 이번 판독에서 추출하지 않았다. 필요하면 재료표 추출을 별도 Task로 한다.",
|
||||
"- 철근 배치는 골격 LOD 범위 밖이라 모델에 넣지 않았다 (사용자 승인 3D 상세 수준: 축선·표준단면 스윕 골격).",
|
||||
"- 설계 조건과 상이하면 구조해석 검토 후 철근 보강 가능 (주기 5, 시공 중 판단 대상)\n",
|
||||
"## 6. 확인 요청 (HITL)",
|
||||
"1. R1 6.892 m 와 6.982 m 중 설계 기준값은 무엇인지",
|
||||
"2. 라이닝 패턴군과 굴착 패턴명의 대응표가 있는지",
|
||||
"3. 측점별 지보패턴 구간표가 확보되면 그 값으로 placeholder 구간을 교체할지",
|
||||
""]
|
||||
open(OUT, "w", encoding="utf-8").write("\n".join(lines))
|
||||
print("written", OUT, len(lines), "lines")
|
||||
Reference in new issue
Block a user