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>
This commit is contained in:
ahnyongjooandClaude Sonnet 5.5 committed 2026-10-28 11:20:18 +09:00
commit 964a841de1
3978 files changed
+58013356

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
"""Dump DIMENSION entities (defpoints, measurement, text) of a region (read only)."""
import sys
from dxftools import doc
lab = sys.argv[1]
x0, y0, x1, y1 = map(float, sys.argv[2:6])
for e in doc(lab).modelspace().query("DIMENSION"):
p = e.dxf.defpoint
q2, q3 = e.dxf.get("defpoint2"), e.dxf.get("defpoint3")
pts = [v for v in (p, q2, q3) if v is not None]
if not any(x0 <= v.x <= x1 and y0 <= v.y <= y1 for v in pts):
continue
try:
m = e.get_measurement()
except Exception:
m = None
f = lambda v: f"({v.x:.1f},{v.y:.1f})" if v is not None else "-"
print(e.dxf.handle, f"{m:.1f}" if isinstance(m, float) else m, repr(e.dxf.get("text", "")), f(q2), f(q3), f(p), e.dxf.get("angle", 0))
+100
View File
@@ -0,0 +1,100 @@
"""Reading aids for the Ulsan P2 drawing (read only): render a region, dump entities of a region.
usage:
python dxftools.py render <D1|D2|D3> x0 y0 x1 y1 <name> [px] -> 판독/<name>.png
python dxftools.py dump <D1|D2|D3> x0 y0 x1 y1 [layer-regex] -> prints LINE/LWPOLYLINE/ARC/CIRCLE/TEXT
DXF source: data/산외교울산P2/drawings/dxf (converted from 교각배근도(울산P2).dwg).
"""
import re
import sys
from pathlib import Path
import ezdxf
from ezdxf import bbox
HERE = Path(__file__).resolve().parent
DXF = HERE.parent / "교각모델링앱" / "data" / "산외교울산P2" / "drawings" / "dxf"
_CACHE = {}
def doc(label):
if label not in _CACHE:
path = sorted(DXF.glob("*.dxf"))[int(label[1]) - 1]
_CACHE[label] = ezdxf.readfile(path)
return _CACHE[label]
def in_region(e, x0, y0, x1, y1):
try:
b = bbox.extents([e], fast=True)
except Exception:
return False
return b.has_data and b.extmax.x >= x0 and b.extmin.x <= x1 and b.extmax.y >= y0 and b.extmin.y <= y1
def entities(label, x0, y0, x1, y1, layer=None):
rx = re.compile(layer) if layer else None
for e in doc(label).modelspace():
if rx and not rx.search(e.dxf.layer):
continue
if in_region(e, x0, y0, x1, y1):
yield e
def describe(e):
t, L, h = e.dxftype(), e.dxf.layer, e.dxf.handle
r = lambda v: round(v, 1)
if t == "LINE":
s, f = e.dxf.start, e.dxf.end
return f"{h} {L} LINE ({r(s.x)},{r(s.y)})-({r(f.x)},{r(f.y)}) len {r(s.distance(f))}"
if t == "LWPOLYLINE":
pts = [(r(p[0]), r(p[1])) for p in e.get_points()]
return f"{h} {L} PLINE closed={e.closed} {pts}"
if t == "ARC":
c = e.dxf.center
return f"{h} {L} ARC c({r(c.x)},{r(c.y)}) r {r(e.dxf.radius)} {r(e.dxf.start_angle)}..{r(e.dxf.end_angle)}"
if t == "CIRCLE":
c = e.dxf.center
return f"{h} {L} CIRCLE c({r(c.x)},{r(c.y)}) r {r(e.dxf.radius)}"
if t in ("TEXT", "MTEXT"):
p = e.dxf.insert
s = e.plain_text() if t == "MTEXT" else e.dxf.text
return f"{h} {L} {t} ({r(p.x)},{r(p.y)}) '{s.strip()}'"
if t == "INSERT":
p = e.dxf.insert
return f"{h} {L} INSERT {e.dxf.name} ({r(p.x)},{r(p.y)})"
return f"{h} {L} {t}"
def render(label, x0, y0, x1, y1, name, px=2400):
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from ezdxf.addons.drawing import Frontend, RenderContext
from ezdxf.addons.drawing.config import BackgroundPolicy, ColorPolicy, Configuration
from ezdxf.addons.drawing.matplotlib import MatplotlibBackend
d = doc(label)
keep = list(entities(label, x0, y0, x1, y1))
fig = plt.figure(figsize=(px / 100, px * (y1 - y0) / (x1 - x0) / 100), dpi=100)
ax = fig.add_axes([0, 0, 1, 1])
cfg = Configuration(background_policy=BackgroundPolicy.WHITE, color_policy=ColorPolicy.BLACK)
Frontend(RenderContext(d), MatplotlibBackend(ax), config=cfg).draw_entities(keep)
ax.set_xlim(x0, x1)
ax.set_ylim(y0, y1)
ax.set_aspect("equal")
ax.axis("off")
out = HERE / "판독" / f"{name}.png"
out.parent.mkdir(exist_ok=True)
fig.savefig(out, facecolor="white")
plt.close(fig)
return out
if __name__ == "__main__":
cmd, label = sys.argv[1], sys.argv[2]
x0, y0, x1, y1 = map(float, sys.argv[3:7])
if cmd == "render":
print(render(label, x0, y0, x1, y1, sys.argv[7], int(sys.argv[8]) if len(sys.argv) > 8 else 2400))
else:
for e in entities(label, x0, y0, x1, y1, sys.argv[7] if len(sys.argv) > 7 else None):
print(describe(e))
+107
View File
@@ -0,0 +1,107 @@
"""R2 (column rebar) reading of the Ulsan P2 drawing (배근도(4)) -> data/산외교울산P2 params.column_rebar / rebar_marks /
display palette, expected.G2; adds G2 to the pier scope. Same form as P1 (script copied 2026-10-15); P2 differences read from the sheet:
main P1 A 32.110 (P1 27.510), hoops P3 12 / P4 31 / P2 82 (chains 625, 11@150=1.650, 31@150=4.650, 82@300=24.600), ties P5 19 levels x 4 = 76
(chain 150, 625, 3@1.800 + 13@1.800 = 16 x 1.800, 1.200, 900; ends at the hoop chain top FB + 31.675 m like P1). Original P1 docstring follows.
배근도(1) (sheet x 0..46000): front view footing bottom y -40253.3, column CL x 10473.9; sections C-C / D-D / E-E.
- main P1: 68 bars (CSBSEC1 inserts) on r 1125, pitch 360/68, start 0 deg; drawn 0/180-deg bars: vertical from
FB+540, R340 bend (B041/B042), leg at FB+200 outwards; schedule A 27.510, B 534 (= pi/2 x 340), C 500
- hoops R 1.149 (schedule; drawn arc 1147.5): chain '150','625','11@150=1.650','25@150=3.750','70@300=21.000'
-> P3 (V, coupler 55) 12 levels in the footing, P4 (X, coupler 55) 25, P2 (VI, laps 380, hooks 200) 70
- ties P5 (I, 2.295 + 2 x 150): chain '150','625','3@1.800','11@1.800','1.200' -> 16 levels x 4 (64);
section lines at 0 / 42.4 / 90 / 132.4 deg (bar positions 0, 8, 17, 25)
"""
import json
import shutil
import sys
import time
from pathlib import Path
HERE = Path(__file__).resolve().parent
APP = HERE.parent / "교각모델링앱"
PIER = APP / "data" / "산외교울산P2"
FB = 38916
S = json.loads((HERE / "판독" / "schedule.json").read_text(encoding="utf-8"))
ROWS = {r["mark"]: r for t in S.values() for r in t["rows"]}
def L(m):
"""Schedule length (first value right of J/R columns) in mm."""
return round(float(ROWS[m]["_right"][-2][1]) * 1000)
def chain(start, groups, base):
z = base + sum(start)
out = [z]
for n, p in groups:
for _ in range(n):
z += p
out.append(z)
return out
def main():
P = json.loads((PIER / "params.json").read_text(encoding="utf-8"))
E = json.loads((PIER / "expected.json").read_text(encoding="utf-8"))
stamp = time.strftime("%Y%m%d_%H%M%S")
(PIER / "backup").mkdir(exist_ok=True)
for f in ("params.json", "expected.json"):
shutil.copy2(PIER / f, PIER / "backup" / f"{Path(f).stem}_before_R2_{stamp}.json")
hoop_chain = {"start": [150, 625], "groups": [[11, 150], [31, 150], [82, 300]]}
levels = chain(hoop_chain["start"], hoop_chain["groups"], FB)
zones = [("P3", 12), ("P4", 31), ("P2", 82)]
cut = [0, 12, 43, 125]
tie_chain = {"start": [150, 625], "groups": [[16, 1800], [1, 1200], [1, 900]]}
a = lambda m: round(float(ROWS[m]["A"]) * 1000) if "." in ROWS[m]["A"] else int(ROWS[m]["A"])
P["column_rebar"] = {
"form": "ring",
"schedule": {m: {k: v for k, v in ROWS[m].items() if k not in ("y", "_right")} | {"L": ROWS[m]["_right"][-2][1]}
for m in ("P1", "P2", "P3", "P4", "P5")},
"main": {"mark": "P1", "count": 68, "start_deg": 0.0, "radius": 1125.0, "hook_leg_z": FB + 200, "bend_r": 340,
"hook_leg": 500, "straight_from": FB + 540, "straight_len": a("P1"),
"src": "배근도(1) 단면 C-C/D-D/E-E CSBSEC1 68개 r 1,125 (start 0 deg); 정면 B041/B042 R340, A91D/A920 시작 FB+540; 재료표 P1"},
"hoops": {"radius": 1149, "zones": [
{"mark": "P3", "type": "coupler_half", "levels": levels[cut[0]:cut[1]], "a": a("P3"), "gap": int(ROWS["P3"]["B"])},
{"mark": "P4", "type": "coupler_ring", "levels": levels[cut[1]:cut[2]], "a": a("P4"), "gap": int(ROWS["P4"]["B"])},
{"mark": "P2", "type": "lap_half", "levels": levels[cut[2]:cut[3]], "a": a("P2"), "lap": int(ROWS["P2"]["J"]),
"hook": int(ROWS["P2"]["B"])}],
"src": "재료표 R 1.149 (단면 원호 작도 1,147.5); 정면 사슬 '150','625','11@150','25@150','70@300'; 이음 위치 [추론]"},
"ties": {"mark": "P5", "levels": chain(tie_chain["start"], tie_chain["groups"], FB),
"angles_deg": [0.0, 8 * 360 / 68, 90.0, 25 * 360 / 68], "straight": a("P5"), "hook": int(ROWS["P5"]["B"]),
"z_offset": 16 / 2 + 19 / 2,
"src": "정면 사슬 '3@1.800','11@1.800','1.200' (+ 첫 단 775) = 16단 x 4 = 64; 단면 선 0/42.4/90/132.4 deg; 갈고리 아래 [추론]"},
}
P["rebar_marks"] = {m: {"dia": r["dia"], "shape": r["shape"]} for m, r in ROWS.items()}
P["rebar_marks_src"] = "배근도(3) 철근재료표 SD400·SD500 (34 마크)"
pal = P["display"]["rebar_palette"]
pal.setdefault("22", [23, 190, 207])
pal.setdefault("13", [140, 86, 75])
P["display"]["column_mark_colours"] = {"P1": [200, 40, 40], "P2": [40, 120, 200], "P3": [40, 170, 170],
"P4": [90, 90, 220], "P5": [140, 60, 160]}
P["display"]["src"] = "R8 팔레트(2026-10-07) + 울산 확장 H22 (23,190,207), H13 (140,86,75) [2026-10-13]; 숨김 없음"
P["scope"] = sorted(set(P["scope"]) | {"G2"})
P["builders"]["G2"] = "column_rebar_ring.py"
E["rebar_palette"] = dict(E["rebar_palette"], **{"22": [23, 190, 207], "13": [140, 86, 75]})
E["checkers"]["G2"] = "g2_ring.py"
E["negative"]["G2"] = [{"mark": "P1", "d": [5, 0, 0]}, {"mark": "P2", "d": [0, 0, 5]}]
E["G2"] = {
"schedule_L": {m: L(m) for m in ("P1", "P2", "P3", "P4", "P5")},
"schedule_n": {m: int(ROWS[m]["n"]) for m in ("P1", "P2", "P3", "P4", "P5")},
"dia": {m: int(ROWS[m]["dia"][1:]) for m in ("P1", "P2", "P3", "P4", "P5")},
"main": {"mark": "P1", "radius": 1125, "divisions": 68, "start_deg": 0.0, "bottom_above_footing": 200,
"top_parts": [540, a("P1")]},
"hoop_chain": hoop_chain, "hoop_zones": zones, "r_hoop": 1149,
"tie": {"mark": "P5", "chain": tie_chain, "per_level": 4, "offset": 17.5},
"no_fillet_marks": ["P1", "P3", "P4"],
"src": "재료표 L·수량·R; 단면 '125','1.125','1.125','125', DIVISION 68; 정면 사슬(띠철근 외측·크로스타이); P1 갈고리 작도 높이 200/540",
}
(PIER / "params.json").write_text(json.dumps(P, ensure_ascii=False, indent=1), encoding="utf-8")
(PIER / "expected.json").write_text(json.dumps(E, ensure_ascii=False, indent=1), encoding="utf-8")
print("hoop levels", len(levels), levels[0], levels[-1], "ties", len(P["column_rebar"]["ties"]["levels"]),
P["column_rebar"]["ties"]["levels"][-1])
print("schedule L", E["G2"]["schedule_L"], "scope", P["scope"])
if __name__ == "__main__":
main()
+130
View File
@@ -0,0 +1,130 @@
"""R3 (footing rebar) reading of the Ulsan P2 drawing (배근도(4)) -> data/산외교울산P2 params.footing_rebar, expected.G3; adds G3 to the
pier scope (builder footing_rebar_mats.py, checker g3_mats.py). Same form as P1 (extract_r3.py there), P2 values (2026-10-15).
Footing 7,800 (X, front view) x 7,500 (Y, side view) x 2,500, bars in the FOUNDATION's own axes (the foundation is rotated 37 deg, G1);
the engine turns them with the footing. Front view footing x 5650..14078 (CX 9937), side view x 19802..27931 (CX 23940), y_bottom -39919.
- position chains (front, X): top '100','300','28@250=7.000','300','100' -> -3800 | -3500..3500 @250 | 3800 (31 = B1);
bottom '100','2@150','56@125=7.000','2@150','100' -> -3800,-3650 | -3500..3500 @125 | 3650,3800 (61 = B2 31 on top positions + B3 30)
- side (Y): top '100','150','28@250=7.000','150','100' -> -3650 | -3500..3500 @250 | 3650 (31 = B4); bottom '100','150','56@125=7.000',
'150','100' -> -3650 | -3500..3500 @125 | 3650 (59 = B5 31 on top positions + B6 28)
- layer heights: drawn lines z 150 / 1,275 / 2,400 (same as P1) -> Y-direction bottom 127.5, X bottom 172.5, top Y 2422.5 / X 2377.5 [derived,
P1 rule]; U bars B2/B5 legs up 2,250, lap J 1,160 (length only); L = A + 2B + J reproduces 12.960 / 13.260
- B8 perimeter hoops z 375..2,175 @300 (7; chain '225','6@300=1.800','225' + 150), centreline inset 77.5 (P1 rule) -> half 3822.5 x 3672.5;
schedule L 33.330 vs 2A + 2B + 4J = 32,190 (P1 formula reproduces its own L) -> drawing / table conflict, recorded as planned FAIL
- B9 (X, 7) / B10 (Y, 8) mid layer z 1,275, legs 100 down: positions are not dimensioned for these marks -> symmetric 1,000 pitch [추론]
- B7 verticals (XI: A 2,285, legs 78 / 52, L 2,415): drawn in elevation at the 22 front x positions and 20 side y positions (CS-RBAR-EREC
lines 2,250 long, z 150..2,400): chain '100','300','500','8@250','4@500','8@250','500','300','100' (front) / '100','400','500','7@250',
'4@500','7@250','500','400','100' (side) without the centre point 0 -> 22 x 20 = 440 grid points; schedule 320 = 440 minus the points with
|x| <= 2,000 and |y| <= 1,750 (120) [추론: the only rectangle that gives 320 besides its transpose]; z0 = 150 + (2,250 - 2,285) / 2 = 132.5
"""
import json
import shutil
import time
from pathlib import Path
HERE = Path(__file__).resolve().parent
APP = HERE.parent / "교각모델링앱"
PIER = APP / "data" / "산외교울산P2"
S = json.loads((HERE / "판독" / "schedule.json").read_text(encoding="utf-8"))
ROWS = {r["mark"]: r for t in S.values() for r in t["rows"]}
MARKS = [f"B{i}" for i in range(1, 11)]
GRID_X = [-3500, -3000, -2750, -2500, -2250, -2000, -1750, -1500, -1250, -1000, -500, 500, 1000, 1250, 1500, 1750, 2000, 2250, 2500, 2750, 3000, 3500]
GRID_Y = [-3250, -2750, -2500, -2250, -2000, -1750, -1500, -1250, -1000, -500, 500, 1000, 1250, 1500, 1750, 2000, 2250, 2500, 2750, 3250]
assert len(GRID_X) == 22 and len(GRID_Y) == 20
ROW_DEF = {
"bottom_front": {"first": [-3800, -3650], "start": -3500, "pitch": 125, "n": 57, "last": [3650, 3800]},
"top_front": {"first": [-3800], "start": -3500, "pitch": 250, "n": 29, "last": [3800]},
"bottom_side": {"first": [-3650], "start": -3500, "pitch": 125, "n": 57, "last": [3650]},
"top_side": {"first": [-3650], "start": -3500, "pitch": 250, "n": 29, "last": [3650]},
"grid_x": {"first": GRID_X, "start": 0, "pitch": 1, "n": 0},
"grid_y": {"first": GRID_Y, "start": 0, "pitch": 1, "n": 0},
}
MID_X = [-3500 + 1000 * i for i in range(8)] # B10 (8 bars along Y), positions along X [추론]
MID_Y = [-3000 + 1000 * i for i in range(7)] # B9 (7 bars along X), positions along Y [추론]
EXCLUDE = {"x": 2000, "y": 1750}
def row(spec):
return sorted(spec.get("first", []) + [spec["start"] + spec["pitch"] * i for i in range(spec["n"])] + spec.get("last", []))
def mm(v):
return round(float(v) * 1000) if "." in v else int(v)
def L(m):
return round(float(ROWS[m]["_right"][0][1]) * 1000) # first value right of the J column = L
def main():
P = json.loads((PIER / "params.json").read_text(encoding="utf-8"))
E = json.loads((PIER / "expected.json").read_text(encoding="utf-8"))
stamp = time.strftime("%Y%m%d_%H%M%S")
for f in ("params.json", "expected.json"):
shutil.copy2(PIER / f, PIER / "backup" / f"{Path(f).stem}_before_R3_{stamp}.json")
bf, tf, bs_, ts = (row(ROW_DEF[k]) for k in ("bottom_front", "top_front", "bottom_side", "top_side"))
assert len(bf) == 61 and len(tf) == 31 and len(bs_) == 59 and len(ts) == 31
on_f = [v for v in bf if v in tf]
off_f = [v for v in bf if v not in tf]
on_s = [v for v in bs_ if v in ts]
off_s = [v for v in bs_ if v not in ts]
leg_u, lap, hook = mm(ROWS["B2"]["B"]), mm(ROWS["B2"]["J"]), mm(ROWS["B9"]["B"])
a_y, a_x = mm(ROWS["B1"]["A"]), mm(ROWS["B4"]["A"]) # Y-direction bars 7,300, X-direction bars 7,600
src_f, src_s = "배근도(4) 정면도 기초", "배근도(4) 측면도 기초"
groups = [
dict(mark="B1", axis="Y", z=2422.5, pos=tf, a=a_y, leg=0, src=f"{src_f} 상면 사슬 '28@250' (끝 3,800)"),
dict(mark="B2", axis="Y", z=127.5, pos=on_f, a=a_y, leg=leg_u, lap=lap, src=f"{src_f} 하면 U형 (상면 위치), 이음 J {lap} 길이만"),
dict(mark="B3", axis="Y", z=127.5, pos=off_f, a=a_y, leg=0, src=f"{src_f} 하면 '2@150','56@125' 중 B2 외"),
dict(mark="B4", axis="X", z=2377.5, pos=ts, a=a_x, leg=0, src=f"{src_s} 상면 사슬 '28@250' (끝 3,650)"),
dict(mark="B5", axis="X", z=172.5, pos=on_s, a=a_x, leg=leg_u, lap=lap, src=f"{src_s} 하면 U형 (측면 상면 위치) [추론]"),
dict(mark="B6", axis="X", z=172.5, pos=off_s, a=a_x, leg=0, src=f"{src_s} 하면 B5 외"),
dict(mark="B9", axis="X", z=1275, pos=MID_Y, a=a_x, leg=-hook, src=f"{src_s} 중앙: 위치 치수 없음, 1,000 간격 대칭 [추론]"),
dict(mark="B10", axis="Y", z=1275, pos=MID_X, a=a_y, leg=-hook, src=f"{src_f} 중앙: 위치 치수 없음, 1,000 간격 대칭 [추론]; B9와 같은 높이"),
]
hoop = dict(mark="B8", half_x=3822.5, half_y=3672.5, levels=[375 + 300 * i for i in range(7)],
design_length=2 * mm(ROWS["B8"]["A"]) + 2 * mm(ROWS["B8"]["B"]) + 4 * mm(ROWS["B8"]["J"]),
src="정면·측면 둘레 띠, z 375~2,175 @300; 중심선 안쪽 77.5 (울산P1 규칙); 길이 2A+2B+4J")
pts = [[x, y] for x in GRID_X for y in GRID_Y if not (abs(x) <= EXCLUDE["x"] and abs(y) <= EXCLUDE["y"])]
b_len, c_len = mm(ROWS["B7"]["B"]), mm(ROWS["B7"]["C"])
vert = dict(mark="B7", points=pts, z0=150 + (2250 - mm(ROWS["B7"]["A"])) / 2, a=mm(ROWS["B7"]["A"]), leg_bottom=b_len, leg_top=c_len,
leg_axis="Y", src="입면 CS-RBAR-EREC 22 x 20 위치(중앙점 0 제외) 중 |x|<=2,000 & |y|<=1,750 제외 = 320 [추론]; 작도 z 150~2,400, A 중앙 배치; 다리 78 / 52 +Y [추론]")
P["footing_rebar"] = {"form": "mats", "groups": groups, "hoops": hoop, "verticals": vert,
"src": "배근도(4) 정면·측면 기초 배근 + 재료표 B1~B10 (기초 자체 축, 기초 회전은 footing.rot_deg)"}
P["scope"] = [g for g in ("G1", "G2", "G3", "G4", "G5") if g in set(P["scope"]) | {"G3"}]
P["builders"]["G3"] = "footing_rebar_mats.py"
P["rebar_marks"] = {m: {"dia": r["dia"], "shape": r["shape"]} for m, r in ROWS.items()}
assert "25" in P["display"]["rebar_palette"], "H25 colour missing"
E["checkers"]["G3"] = "g3_mats.py"
E["negative"]["G3"] = [{"mark": "B3", "d": [5, 0, 0]}, {"mark": "B7", "d": [0, 0, 5]}]
E["G3"] = {
"schedule": {m: [L(m), int(ROWS[m]["n"])] for m in MARKS},
"dia": {m: int(ROWS[m]["dia"][1:]) for m in MARKS},
"rows": ROW_DEF,
"layers": {
"B1": {"axis": "Y", "z": 2422.5, "row": "top_front"},
"B2": {"axis": "Y", "z": 127.5, "row": "bottom_front", "subset": "on", "of": "top_front"},
"B3": {"axis": "Y", "z": 127.5, "row": "bottom_front", "subset": "off", "of": "top_front"},
"B4": {"axis": "X", "z": 2377.5, "row": "top_side"},
"B5": {"axis": "X", "z": 172.5, "row": "bottom_side", "subset": "on", "of": "top_side"},
"B6": {"axis": "X", "z": 172.5, "row": "bottom_side", "subset": "off", "of": "top_side"},
"B9": {"axis": "X", "z": 1275, "pos": MID_Y, "leg": -hook},
"B10": {"axis": "Y", "z": 1275, "pos": MID_X, "leg": -hook},
},
"hoops": {"mark": "B8", "half_x": 3822.5, "half_y": 3672.5, "chain": {"start": [375], "groups": [[6, 300]]}},
"verticals": {"mark": "B7", "grid_x": "grid_x", "grid_y": "grid_y", "exclude": EXCLUDE, "z0": vert["z0"], "a": vert["a"],
"legs": {"bottom": b_len, "top": c_len}},
"src": "재료표 L·수량; 정면·측면 위치 사슬(상면·하면·중앙); 층 높이는 작도값 [derived]",
}
E["planned_fail"] = [pf for pf in E.get("planned_fail", []) if pf.get("gate") != "G3"] + [
{"gate": "G3", "item": "B8 original length vs L (worst)",
"reason": f"재료표 B8 L 33.330 vs 2A+2B+4J = {hoop['design_length']} (울산P1 B8은 같은 식이 L과 일치) — 도면·재료표 불일치 의심, 설계자 확인 대상(OPEN #32)",
"approved": "미승인 — Agent 제안, 사용자 확인 대기"}]
(PIER / "params.json").write_text(json.dumps(P, ensure_ascii=False, indent=1), encoding="utf-8")
(PIER / "expected.json").write_text(json.dumps(E, ensure_ascii=False, indent=1), encoding="utf-8")
print("counts", {g["mark"]: len(g["pos"]) for g in groups}, "B7", len(pts), "B8 len", hoop["design_length"], "B7 z0", vert["z0"])
print("schedule n", {m: int(ROWS[m]["n"]) for m in MARKS})
if __name__ == "__main__":
main()
+288
View File
@@ -0,0 +1,288 @@
"""R4 (coping rebar C1~C15, bearing-seat reinforcement S1~S4) of the Ulsan P2 drawing (derived from the P1 script, see make_r4.py) ->
data/산외교울산P2 params.coping_rebar (explicit centreline polylines), expected.G4; adds G4 to the scope.
Sources (배근도(1) front view CX 10473.9, side view CX 24604.3; 배근도(2) sections A-A / B-B, 받침부 보강 상세 1·3,
F-F / G-G; 배근도(3) schedule and shape table). Front-view drawing y -> EL: z = y + 99715.8 (bottom bars
-31753.3 = 67962.5 = coping bottom + 100; layer 1 -28752.8 = right top - 100).
- C1/C3 layer 1 (z 70962.5), C2/C4 layer 2 (z 70862.5): drawn lines 9,258.6 / 9,297 (= A); end legs drawn
(C1 left 1,364 / right 1,369 along the end faces; C2 102); y 24@125 (25 per layer) - C1/C2 even, C3/C4 odd [추론]
- C15 left top (z 71092.5): 4,500 + legs 510 (left leg along the end face [추론], right leg at x -100); y 12@250
- C5/C6 bottom: drawn polyline (650 / 4,018 / 2,457 / 4,016 / 650); y 12@250, C5 even (7) / C6 odd (6) [추론]
- stirrups C7 (x -4,600..-1,400 @200, 17) / C8 (-1,200 -1,000 -500 0 500 1,000 1,200) / C9 (1,400..4,600):
3 per position, legs y (-1,500,-250) (-1,000,1,000) (250,1,500) (A-A; shape table II note a = (1,250x2 +
2,000)/3 + 54); centreline height b = drawn height + 54 (schedule B range reproduced to 1 mm): top = layer-1
(x >= 0) or C15 level (x <= -500) + 27, bottom = drawn bottom bar line - 27
- side bars 13 levels z 70,562.5 - 200k (side view / A-A '530','13@200'), drawn ends per level; y +-977.5 and
+-272.5 = C13 (levels 1-5, 20) / C11 (levels 6-13, 32), a = drawn + 44 (schedule range reproduced);
180-deg hooks (b 165, c 140) in plan towards y = 0 inside the drawn ends [추론]
- perimeter hoops y +-1,477.5: C12 (XII, plan corners R400, levels 1-6) / C10 (III, levels 7-13); counts kept,
schedule ranges suggest 5 / 8 levels (C10 a max 9,570 = level 6) -> drawing inconsistency recorded
- C14 (H16, 3,000 + 300) at the drawn section marks (5 levels, 8+4+4+3+1); legs along X towards the column and
x offset 19 from the stirrup plane [추론]
- S1/S2 (front bearings 1,2) S3/S4 (rear 3,4): 11 bars per direction (50,100,'8@125',100,50) in the 1,300 pedestal,
50 below the pedestal top, legs 700 down; S1/S3 along Y on top, S2/S4 along X below [추론]
"""
import json
import shutil
import time
from pathlib import Path
HERE = Path(__file__).resolve().parent
APP = HERE.parent / "교각모델링앱"
PIER = APP / "data" / "산외교울산P2"
S = json.loads((HERE / "판독" / "schedule.json").read_text(encoding="utf-8"))
ROWS = {r["mark"]: r for t in S.values() for r in t["rows"]}
MARKS = [f"C{i}" for i in range(1, 16)] + ["S1", "S2", "S3", "S4"]
Z0 = 99715.8 + 853 # P1 drawing y -> z, shifted by the P2 coping bottom (68716 - 67863)
DIA = {"H32": 32, "H22": 22, "H16": 16, "H13": 13}
def z(y):
return round(y + Z0, 1)
def mm(v):
return round(float(v) * 1000) if "." in v else int(v)
def L(m):
"""Schedule length: the value in the L column (sheet x 111,000..112,500)."""
return round(float(next(v for x, v in ROWS[m]["_right"] if 110000 < x < 111100)) * 1000)
def rmin(dia):
d = DIA[dia]
return ((2 * d if d <= 16 else 3.5 * d) + d) / 2
# ---- drawn data (front view, x relative to CX) ----
LAYER1 = dict(z=z(-28752.8), pts=[(-4887.5, z(-30094.5)), (-4640.6, z(-28752.8)), (4618.0, z(-28752.8)), (4886.4, z(-30095.0))])
LAYER2 = dict(z=z(-28852.8), pts=[(-4677.4, z(-28952.8)), (-4659.0, z(-28852.8)), (4638.0, z(-28852.8)), (4658.0, z(-28952.8))])
BOTTOM = [(-4769.8, z(-29455.0)), (-4887.5, z(-30094.5)), (-1228.4, z(-31753.3)), (1228.4, z(-31753.3)),
(4886.4, z(-30095.0)), (4758.9, z(-29457.4))]
C15_Z = z(-28623.3)
C15_X = (-4616.7, -100.0)
LEVELS = [(z(y), a, b) for y, a, b in (
(-29153.3, -4714.3, 4698.1), (-29353.3, -4751.1, 4738.1), (-29553.3, -4787.9, 4778.1), (-29753.3, -4824.7, 4818.0),
(-29953.3, -4861.5, 4858.0), (-30153.3, -4757.8, 4757.8), (-30353.3, -4316.6, 4316.6), (-30553.3, -3875.4, 3875.5),
(-30753.3, -3434.3, 3434.3), (-30953.3, -2993.1, 2993.1), (-31153.3, -2551.9, 2551.9), (-31353.3, -2110.7, 2110.7),
(-31553.3, -1669.6, 1669.6))]
STIR_X = {"C7": [-4600 + 200 * i for i in range(17)], "C8": [-1200, -1000, -500, 0, 500, 1000, 1200],
"C9": [1400 + 200 * i for i in range(17)]}
STIR_Y = [(-1500, -250), (-1000, 1000), (250, 1500)]
C14_PTS = [(x, z(-28875.3)) for x in (-4600, -3600, -2600, -1600, 1600, 2600, 3600, 4600)] + \
[(x, z(-29353.3)) for x in (-4600, -2600, 1600, 3600)] + [(x, z(-29553.3)) for x in (-3600, -1600, 2600, 4600)] + \
[(x, z(-30553.3)) for x in (-2600, 1600, 3600)] + [(-1600, z(-31553.3))]
SEAT_OFFS = [-600] + [-500 + 125 * i for i in range(9)] + [600]
ZTL, ZTR, ZTIP = 72046.0, 71916.0, 70416.0
def x_face(z, x):
"""End face |x| of the coping at level z (batter 300 above the tip; vertical tip below)."""
if z < ZTIP:
return 5000.0
return 4700 + 300 * ((ZTL - z) / 1630 if x < 0 else (ZTR - z) / 1500)
def lim_x(y, z, x):
"""Largest |x| of a bar centreline at (y, z) inside the R500 plan corner with 100 clearance (R400 arc)."""
dy = abs(y) - 1100
if dy <= 0:
return 1e9
return x_face(z, x) - 500 + (400 ** 2 - min(dy, 400) ** 2) ** 0.5
def lim_y(x, z):
dx = abs(x) - (x_face(z, x) - 500)
if dx <= 0:
return 1e9
return 1100 + (400 ** 2 - min(dx, 400) ** 2) ** 0.5
def clip_end(pts, y):
"""Shift the first / last straight run ends inward to the corner limit; legs (first / last point) move along."""
pts = [list(p) for p in pts]
for i_end, i_leg in ((1, 0), (-2, -1)):
x, zz = pts[i_end]
lim = lim_x(y, zz, x)
if abs(x) > lim:
d = (abs(x) - lim) * (1 if x > 0 else -1)
pts[i_end][0] -= d
pts[i_leg][0] -= d
return [tuple(p) for p in pts]
def drawn_bottom(x):
"""Drawn bottom-bar line (front view) at x: flat 67,962.5 inside +-1,228.4, slope 1,658.8 / 3,659.1 outside."""
zf = z(-31753.3)
return zf if abs(x) <= 1228.4 else zf + (abs(x) - 1228.4) * 1658.8 / 3659.1
def stir_top_drawn(x):
return C15_Z if x <= -500 else LAYER1["z"]
def hook_bar(xl, xr, y, zz, r, tail):
"""Straight bar with 180-deg plan hooks towards y = 0; the hook extremes sit at xl / xr."""
s = -1 if y > 0 else 1
yy = y + s * 2 * r
return [(xl + r + tail, yy, zz), (xl, yy, zz), (xl, y, zz), (xr, y, zz), (xr, yy, zz), (xr - r - tail, yy, zz)]
def main():
P = json.loads((PIER / "params.json").read_text(encoding="utf-8"))
E = json.loads((PIER / "expected.json").read_text(encoding="utf-8"))
stamp = time.strftime("%Y%m%d_%H%M%S")
for f in ("params.json", "expected.json"):
shutil.copy2(PIER / f, PIER / "backup" / f"{Path(f).stem}_before_R4_{stamp}.json")
R = {m: ROWS[m] for m in MARKS}
dia = {m: R[m]["dia"] for m in MARKS}
bars = []
def add(mark, occ, pts, design, src, r=None):
bars.append(dict(mark=mark, occ=occ, pts=[[round(c, 2) for c in p] for p in pts],
r=rmin(dia[mark]) if r is None else r, design_length=round(design, 1), src=src))
# top layers C1~C4
for lay, (ma, mb), keys in ((LAYER1, ("C1", "C3"), ("A", "B", "C", "J")), (LAYER2, ("C2", "C4"), ("A", "B", "C", "J"))):
for i in range(25):
y = -1500 + 125 * i
m = ma if i % 2 == 0 else mb
add(m, f"y{y:+.0f}", [(x, y, zz) for x, zz in clip_end(lay["pts"], y)], sum(mm(R[m][k]) for k in keys),
f"정면 {'1' if lay is LAYER1 else '2'}단 작도선; 측면 '24@125'; 교번 배치 [추론]")
# C15 left top
dx, dz = -300 / 1657.4, -1630 / 1657.4
leg = mm(R["C15"]["B"])
for i in range(13):
y = -1500 + 250 * i
x0, x1 = C15_X
x0 = max(x0, -lim_x(y, C15_Z, x0))
pts = [(x0 + dx * leg, y, C15_Z + dz * leg), (x0, y, C15_Z), (x1, y, C15_Z), (x1, y, C15_Z - leg)]
add("C15", f"y{y:+.0f}", pts, mm(R["C15"]["A"]) + 2 * leg, "정면 좌측 상면 작도선 4,516.7; 단면 A-A '12@250'; 좌측 다리 단부면 평행 [추론]")
# bottom C5/C6
for i in range(13):
y = -1500 + 250 * i
m = "C5" if i % 2 == 0 else "C6"
pts = [list(p) for p in BOTTOM]
for i_end, i_leg, i_in in ((1, 0, 2), (4, 5, 3)):
(xe, ze), (xi, zi) = pts[i_end], pts[i_in]
lim = lim_x(y, ze, xe)
if abs(xe) > lim:
t = (lim - abs(xi)) / (abs(xe) - abs(xi))
nx, nz = xi + t * (xe - xi), zi + t * (ze - zi)
pts[i_leg] = [pts[i_leg][0] + nx - xe, pts[i_leg][1] + nz - ze]
pts[i_end] = [nx, nz]
add(m, f"y{y:+.0f}", [(x, y, zz) for x, zz in pts], sum(mm(R[m][k]) for k in "ABCDEJ"),
"정면 하면 작도선 (650/4,018/2,457/4,016/650); 단면 '12@250'; 교번 [추론]")
# stirrups C7~C9
stir_exp, clipped = {}, set()
for m, xs in STIR_X.items():
for x in xs:
zt = stir_top_drawn(x) + 27
zb = drawn_bottom(x) - 27
stir_exp[f"{x:.0f}"] = round(zt - zb, 1)
ly = lim_y(x, zt)
for k, (y0, y1) in enumerate(STIR_Y):
y0, y1 = max(y0, -ly), min(y1, ly)
if (y0, y1) != STIR_Y[k]:
clipped.add(x)
ym = (y0 + y1) / 2
pts = [(x, ym, zt), (x, y1, zt), (x, y1, zb), (x, y0, zb), (x, y0, zt), (x, ym, zt)]
design = 2 * (y1 - y0 + 54) + 2 * (zt - zb) + 2 * mm(R[m]["C"])
add(m, f"x{x:+.0f}_{k}", pts, design, "정면 스터럽 작도 위치·높이 (+54); 단면 A-A 3조; 형상표 II 주기")
# side bars C13 / C11 and hoops C12 / C10
hr = (mm(R["C11"]["B"]) - DIA["H22"]) / 2 # hook centreline radius 71.5
tail = mm(R["C11"]["C"])
for k, (zz, xl, xr) in enumerate(LEVELS):
m = "C13" if k < 5 else "C11"
a = xr - xl + 44
hooks = 2 * mm(R[m]["B"]) + 2 * mm(R[m]["C"])
for y in (-977.5, -272.5, 272.5, 977.5):
add(m, f"L{k + 1:02d}y{y:+.0f}", hook_bar(xl, xr, y, zz, hr, tail),
a + hooks + (mm(R[m]["J"]) if "J" in R[m] else 0),
f"측면 단 {k + 1} (z {zz}); 정면 작도 끝 {xl}/{xr}; a = 작도 + 44; 평면 갈고리 [추론]", r=hr - 0.5)
hm = "C12" if k < 6 else "C10"
yh = 1477.5
pts = [(0, -yh, zz), (xr, -yh, zz), (xr, yh, zz), (xl, yh, zz), (xl, -yh, zz), (0, -yh, zz)]
if hm == "C12":
design = 2 * (a - 790) + 2 * mm(R[hm]["B"]) + 4 * mm(R[hm]["C"]) + 4 * mm(R[hm]["J"])
add(hm, f"L{k + 1:02d}", pts, design, f"둘레 띠 단 {k + 1}; 모서리 R400; 길이 2a+2b+4c+4j", r=400)
else:
design = 2 * (xr - xl + 54) + 2 * mm(R[hm]["B"]) + 4 * mm(R[hm]["J"])
add(hm, f"L{k + 1:02d}", pts, design, f"둘레 띠 단 {k + 1}; 길이 2a+2b+4j (a = 작도 + 54)")
# C14 cross bars
c14 = []
for x, zz in C14_PTS:
s = 1 if x > 0 else -1
xx = x - s * 19
lg = mm(R["C14"]["B"])
yl = min(1500, lim_y(xx, zz), lim_y(xx - s * lg, zz) + 0)
if yl < 1500:
clipped.add(x)
add("C14", f"x{x:+.0f}z{zz:.0f}", [(xx - s * lg, -yl, zz), (xx, -yl, zz), (xx, yl, zz), (xx - s * lg, yl, zz)],
mm(R["C14"]["A"]) + 2 * lg, "정면 단면 표시 위치; 단면 A-A 3,000; 다리 방향·x 19 이격 [추론]")
c14.append([xx, zz])
# S1~S4
seats = {b["no"]: b for b in P["bearing_seats"]["bearings"]}
lg = mm(R["S1"]["B"])
s_exp = {}
for (my, mx), nos in ((("S1", "S2"), (1, 2)), (("S3", "S4"), (3, 4))):
for no in nos:
b = seats[no]
cx, cy = b["centre"]
top = b["seat_z"] + b["h1"]
z1, z2 = top - 50, top - 50 - DIA["H13"]
for o in SEAT_OFFS:
x = cx + o
add(my, f"B{no}x{o:+.0f}", [(x, cy - 600, z1 - lg), (x, cy - 600, z1), (x, cy + 600, z1), (x, cy + 600, z1 - lg)],
mm(R[my]["A"]) + 2 * lg, f"받침 {no} 보강 상세; '50','100','8@125','100','50'")
y = cy + o
add(mx, f"B{no}y{o:+.0f}", [(cx - 600, y, z2 - lg), (cx - 600, y, z2), (cx + 600, y, z2), (cx + 600, y, z2 - lg)],
mm(R[mx]["A"]) + 2 * lg, f"받침 {no} 보강 상세; S1/S3 아래 단 [추론]")
s_exp[str(no)] = dict(centre=[cx, cy], z_top=z1, marks=[my, mx])
P["coping_rebar"] = {"form": "polylines", "host": "CP", "group": "코핑", "bars": bars,
"src": "배근도(1) 정면·측면·평면, 배근도(2) 단면 A-A/B-B·받침부 보강 상세, 배근도(3) 재료표·형상표"}
P["scope"] = [g for g in ("G1", "G2", "G3", "G4", "G5") if g in set(P["scope"]) | {"G4"}]
P["builders"]["G4"] = "rebar_polylines.py"
E["checkers"]["G4"] = "g4_polylines.py"
E["negative"]["G4"] = [{"mark": "C7", "d": [5, 0, 0]}, {"mark": "C11", "d": [0, 0, 5]}]
E["G4"] = {
"schedule": {m: [L(m), int(R[m]["n"])] for m in MARKS},
"dia": {m: DIA[dia[m]] for m in MARKS},
"fixed_length": ["C1", "C2", "C3", "C4", "C5", "C6", "C14", "C15", "S1", "S2", "S3", "S4"],
"ranges": {"C7": ["B", 1656, 3106], "C8": ["B", 3054, 3184], "C9": ["B", 1526, 2977],
"C11": ["A", 3383, 9560], "C13": ["A", 9456, 9764], "C12": ["A", 8666, 8974], "C10": ["A", 3393, 9570]},
"layers": {"C1": {"z": LAYER1["z"], "y": "top125", "parity": 0}, "C3": {"z": LAYER1["z"], "y": "top125", "parity": 1},
"C2": {"z": LAYER2["z"], "y": "top125", "parity": 0}, "C4": {"z": LAYER2["z"], "y": "top125", "parity": 1},
"C15": {"z": C15_Z, "y": "y250", "parity": None},
"C5": {"z_min": z(-31753.3), "y": "y250", "parity": 0}, "C6": {"z_min": z(-31753.3), "y": "y250", "parity": 1}},
"x_extent": {"C1": [-4887.5, 4886.4], "C3": [-4887.5, 4886.4], "C2": [-4677.4, 4658.0], "C4": [-4677.4, 4658.0],
"C5": [-4887.5, 4886.4], "C6": [-4887.5, 4886.4]},
"rows": {"top125": {"start": -1500, "pitch": 125, "n": 25}, "y250": {"start": -1500, "pitch": 250, "n": 13}},
"stirrups": {"x": STIR_X, "y_pairs": STIR_Y, "height": stir_exp,
"rule": "높이 = 작도 높이 + 54 (상단 1단/C15 선 +27, 하단 하면선 -27)"},
"levels": [[zz, xl, xr] for zz, xl, xr in LEVELS],
"side": {"y": [-977.5, -272.5, 272.5, 977.5], "marks": [["C13", 5], ["C11", 8]]},
"hoops": {"y": 1477.5, "marks": [["C12", 6], ["C10", 7]]},
"c14": {"pts": c14, "y": [-1500, 1500]},
"corner": {"y_abs": 1100, "x_clipped": sorted(clipped),
"rule": "평면 모서리 R500: |y| > 1,100 철근은 R400 원호(중심선 이격 100)까지 단축 - 평면도 작도 [추론]"},
"seats": {"offsets": SEAT_OFFS, "half": 600, "leg": lg, "bearings": s_exp},
"src": "재료표 L·수량·범위; 울산P1 작도 높이(코핑 하면 기준) + 853 = 울산P2 코핑 하면 EL 68.716; 단면 A-A/B-B; 받침부 보강 상세",
}
(PIER / "params.json").write_text(json.dumps(P, ensure_ascii=False, indent=1), encoding="utf-8")
(PIER / "expected.json").write_text(json.dumps(E, ensure_ascii=False, indent=1), encoding="utf-8")
from collections import Counter
cnt = Counter(b["mark"] for b in bars)
print("bars", len(bars), {m: (cnt[m], E["G4"]["schedule"][m][1]) for m in MARKS if cnt[m] != E["G4"]["schedule"][m][1]} or "counts OK")
import statistics
for m in MARKS:
dl = [b["design_length"] for b in bars if b["mark"] == m]
print(m, "L", E["G4"]["schedule"][m][0], "mean", round(statistics.mean(dl), 1), "min", min(dl), "max", max(dl))
print("scope", P["scope"])
if __name__ == "__main__":
main()
+186
View File
@@ -0,0 +1,186 @@
"""S1 (shape) reading of RAG/교각배근도(울산P2).dwg -> app pier data/산외교울산P2 (params, expected), scope G1 + G5.
Same sheet layout as Ulsan P1 (일반도(1), drawn in mm); the P2 differences were found by comparing the text sets and the
entities (2026-10-15): footing 7,800 x 7,500 on 30 steel pipe piles, ROTATED 37.000 deg against the pier axes, column 27.300 m,
all levels shifted; coping outline, bearings (4) and plan positions follow the same rules as P1.
Datums (front view, drawn): column CL x -85062.7; footing bottom y -23099.7 = EL 38.916; coping bottom y -14499.7
(EL 38.916 + 2.500 + 27.300 = 68.716, coping drawn to scale above it). Plan centre (-85062.7, -1608.4) = column circle 1ECD9.
params = drawn entities (handles in src) + ELEVATION / 솔플레이트 TABLE.
expected = dimension / EL texts and tables typed with their text, independent of params.
Rotation: the foundation plan is drawn in the foundation's own axes; the pier CL (arrow) lies at +52.5 deg, so the pier X axis is at
-37 deg in the foundation frame and the foundation is rotated +37 deg (counter-clockwise, X right / Y up) in the pier frame.
The drawing note '구조물 기초는 37.000˚회전' + '회전이 고려된 길이 9.767 m / 9.391 m' (= 7.800/cos37, 7.500/cos37) confirms the angle.
"""
import json
import math
import shutil
import sys
import time
from pathlib import Path
HERE = Path(__file__).resolve().parent
APP = HERE.parent / "교각모델링앱"
PIER = APP / "data" / "산외교울산P2"
sys.path.insert(0, str(HERE))
sys.path.insert(0, str(APP))
import dxftools as T # noqa: E402
from engine import reader # noqa: E402
E1 = T.doc("D1").entitydb
FX, FB_Y, CB_Y = -85062.7, -23099.7, -14499.7
PLAN = (-85062.7, -1608.4)
EL_FB, FT_T, COL_H = 38916, 2500, 27300 # 'EL. 38.916', '2.500', '27.300'
Z_CB = EL_FB + FT_T + COL_H # 68716
ROT_DEG = 37.0 # '구조물 기초는 37.000˚회전'
def line(h):
e = E1[h]
return (e.dxf.start.x, e.dxf.start.y), (e.dxf.end.x, e.dxf.end.y)
def z(y):
return round(Z_CB + (y - CB_Y), 1)
def rx(x):
return round(x - FX, 1)
def tables(texts):
tabs = {t["key"]: t for t in reader.table_candidates(texts)}
el = {int(r["_key"]): r for _, r in reader.table_records(tabs["elevation"]["rows"], str.isdigit) if "EL.'X'" in r}
sp = {int(r["_key"]): r for _, r in reader.table_records(tabs["sole_plate"]["rows"], str.isdigit)
if "S(%)" in r and int(r["_key"]) in el}
return el, sp
def main():
p1 = json.loads((APP / "data" / "산외교울산P1" / "params.json").read_text(encoding="utf-8"))
e1 = json.loads((APP / "data" / "산외교울산P1" / "expected.json").read_text(encoding="utf-8"))
bk = PIER / "backup"
bk.mkdir(exist_ok=True)
stamp = time.strftime("%Y%m%d_%H%M%S")
for f in ("params.json", "expected.json"):
shutil.copy2(PIER / f, bk / f"{Path(f).stem}_before_S1_{stamp}.json")
texts = json.loads((PIER / "drawings" / "texts.json").read_text(encoding="utf-8"))
el, sp = tables(texts)
# ---- drawn entities (handles from the 2026-10-15 dump)
ft, lc = line("1EA54"), line("1EA5A") # footing / lean bottoms, front (7,800 / 8,000)
ft_side, lc_side = line("1EBE5"), line("1EBEA") # side (7,500 / 7,700)
thick = line("1EBE4") # 2,500
col = abs(line("1EA45")[0][0] - line("1EA46")[0][0]) # column outline +-1250
flat = line("1EA1F") # coping bottom flat +-1250
tipL, tipR = line("1EA1E")[1], line("1EA20")[0] # (-5000, 1700) / (+5000, 1700) above the flat
topL, topR = line("1EA2A")[0], line("1EA21")[0] # (-4700, EL 72.046) / (+4700, EL 71.916)
zcb = z(flat[0][1])
assert abs(zcb - Z_CB) < 0.5, zcb
profile = [[-1250.0, zcb], [1250.0, zcb], [rx(tipR[0]), z(tipR[1])], [rx(topR[0]), z(topR[1])], [0.0, z(topR[1])],
[0.0, z(topL[1])], [rx(topL[0]), z(topL[1])], [rx(tipL[0]), z(tipL[1])]]
width = abs(line("1EBC8")[0][0] - line("1EBC8")[1][0])
P = json.loads(json.dumps(p1)) # start from P1 (same form), then replace what differs
P["pier_id"] = "P2"
P["_meta"] = {"structure": "산외교 울산 P2 교각 (T형, 직선 경사 코핑, 강관말뚝 기초 37° 회전)",
"status": "S1 형상 판독 2026-10-15 (Agent 초안, 사용자 확정 전)", "units": "mm; Z = EL x 1000",
"frame": "X 교축직각(정면도 좌→우), Y 교축(평면도 아래→위 = 전열→후열), 원점 = 교각(기둥) 중심. 기초(버림·기초판·말뚝)는 이 축에서 +37° 회전(평면도 반시계)",
"sources": {"D1": "RAG/교각배근도(울산P2).dwg — 일반도(2) + 배근도"}}
P["scope"] = ["G1", "G5"]
P["builders"] = {"G1": "concrete_tapered.py"}
P["lean_concrete"] = {"size_x": round(lc[0][0] - lc[1][0], 1), "size_y": round(lc_side[0][0] - lc_side[1][0], 1),
"z_bottom": EL_FB - 100, "z_top": EL_FB, "src": "일반도 정면 1EA5A (8,000) / 측면 1EBEA (7,700), 두께 100 (기초 자체 좌표계)"}
P["footing"] = {"size_x": round(ft[0][0] - ft[1][0], 1), "size_y": round(ft_side[0][0] - ft_side[1][0], 1),
"z_bottom": EL_FB, "thickness": round(abs(thick[0][1] - thick[1][1]), 1), "rot_deg": ROT_DEG,
"src": "일반도 정면 1EA54 (7,800) / 측면 1EBE5 (7,500), 1EBE4 (2,500); 'EL. 38.916'; 기초 평면도 '구조물 기초는 37.000˚회전' "
"(회전 길이 9.767 m = 7.800/cos37, 9.391 m = 7.500/cos37). 회전 방향: 기초 평면 교각 CL(화살표 +Y)이 +52.5° → 기초가 +37° [추론]"}
P["piles"] = {"count_x": 6, "count_y": 5, "pitch_x": 1300.0, "pitch_y": 1550.0, "diameter": 508.0, "length": 7000.0,
"head_embedment": 100.0, "kind": "강관말뚝 Ø508.0x9T (속찬 원기둥, O5)",
"src": "기초 평면도 '5@1.300=6.500' · '4@1.550=6.200', 가장자리 '650' + 버림 '100'; '(Φ508.0X9T, L=7.0M)'; 말뚝 머리 = 기초 하면 + 100 (정면 말뚝 상단 y -22999.7 vs 기초 하면 -23099.7); 속찬 원기둥은 옥성교 O5 결정 재적용 [추론]"}
P["column"] = {"shape": "circular", "diameter": round(col, 1), "height": COL_H,
"src": "일반도 정면 1EA45/1EA46 (+-1,250), 단면 A-A '2.500'; 높이 '27.300' (파단 작도)"}
c = P["coping"]
c["profile_xz"] = profile
c["width_y"] = round(width, 1)
c["plan_length"] = round(tipR[0] - tipL[0], 1)
c["top_length"] = round(topR[0] - topL[0], 1)
c["z_tip"], c["z_top_left"], c["z_top_right"] = z(tipL[1]), z(topL[1]), z(topR[1])
c["src"] = dict(c["src"], profile="일반도 정면 1EA1F (하면 평탄), 1EA1E/1EA20 (하면 경사), 1EA2A/1EA21 (끝면 300 경사), 1EA1D/1EA1C (상면, 중심 130 단차)",
width="일반도 측면 1EBC8 (3,200)")
# ---- seats (same rules as P1)
bearings = []
for no in sorted(el):
r = el[no]
x = -3250.0 if no in (1, 3) else 3250.0
s1 = int(r["S1"])
y = -(1600 - s1) if no in (1, 2) else (1600 - s1)
bearings.append({"no": no, "type": r["교량받침"], "row": "front" if no in (1, 2) else "rear",
"centre": [x, float(y)], "rotation_deg": 0.0,
"seat_z": float(c["z_top_left"] if x < 0 else c["z_top_right"]),
"el_x": round(float(r["EL.'X'"]) * 1000), "h1": int(r["h1"]), "h2": int(r["h2"]),
"A": int(r["A"]), "B": int(r["B"])})
drawn = {}
for no, hs in ((3, ("1F053", "1F054", "1F055")), (4, ("1F04B", "1F04C", "1F04D")), (1, ("1F063", "1F064", "1F065")),
(2, ("1F05B", "1F05C", "1F05D"))):
(x0, y0), (x1, y1) = line(hs[0])
(_, _), (x2, _) = line(hs[2])
ya, yb = line(hs[0])[0][1], line(hs[0])[1][1]
drawn[no] = [round((x0 + x2) / 2 - PLAN[0], 1), round((ya + yb) / 2 - PLAN[1], 1)]
bs = P["bearing_seats"]
bs["pedestal"] = {"size_x": int(el[1]["C3"]), "size_y": int(el[1]["C6"])}
bs["bearings"], bs["drawn_pedestal_centres"] = bearings, drawn
bs["src"] = dict(bs["src"], position="평면 '1.750','6.500','1.750' (X), S1 (Y); 작도 대좌(1F04B~1F066) 중심과 비교")
rows = []
for no in sorted(sp):
r = sp[no]
rows.append({"no": no, "S": float(r["S(%)"]), "L": int(r["L"]), "W": int(r["W"]), "T": int(r["T"]),
"T1": int(r["T1"]), "T2": int(r["T2"]), "h3": int(el[no]["h3"])})
P["sole_plates"]["table"] = rows
P["rebar_marks_src"] = "철근(G2~G4)은 이번 단계 범위 밖 — 울산 P1 값(사용 안 함)"
E = json.loads(json.dumps(e1))
E["pier_id"] = "P2"
E["_note"] = "독립 기대값: 일반도 치수 문자·EL 문자·표에서 옮긴 값. params와 별도."
E["checkers"] = {"G1": "g1_tapered.py"}
E["negative"] = {"G1": [{"id": "BS02", "d": [3, 0, 0]}, {"id": "BP03", "d": [0, 0, 2]}],
"G5": [{"id": "BB04", "d": [5, 0, 0]}, {"id": "SP03", "d": [0, 0, 5]}]}
E["planned_fail"] = []
for k in [k for k in E if k.startswith("G") and k not in ("G1", "G5")]:
del E[k]
G1 = E["G1"]
G1["footing"] = {"dx": 7800, "dy": 7500, "z0": EL_FB, "thickness": 2500, "rot_deg": ROT_DEG,
"src": "'7.800'(정면·기초 평면), '7.500'(측면·기초 평면), 'EL. 38.916', '2.500', '구조물 기초는 37.000˚회전' (회전 길이 9.767 = 7.800/cos37, 9.391 = 7.500/cos37)"}
G1["lean"] = {"margin": 100, "thickness": 100, "src": "'100','7.800','100' / '100','7.500','100'; 두께 '100'"}
G1["piles"] = {"nx": 6, "ny": 5, "pitch_x": 1300, "pitch_y": 1550, "edge_x": 650, "edge_y": 650, "d": 508, "length": 7000,
"head_embedment": 100, "src": "'5@1.300=6.500','650','100'; '4@1.550=6.200','650','100'; 'Φ508.0X9T, L=7.0M'; 머리 100(작도)"}
G1["column"] = {"d": 2500, "height": 27300, "src": "단면 A-A '2.500', '27.300'"}
G1["coping"] = dict(G1["coping"], z_top_left=72046, z_top_right=71916,
src="'1.250','1.250','3.750' x2; '10.000'; '300','2@4.700=9.400','300'; '1.700'; '3.200'; 'EL. 72.046','EL. 71.916'; 'R=500'")
G1["seats"] = {"table": {str(n): [round(float(r["EL.'X'"]) * 1000), int(r["h1"]), int(r["h2"]), int(r["A"]), int(r["B"])]
for n, r in el.items()},
"pos": {str(n): [-3250 if n in (1, 3) else 3250, -(1600 - int(r["S1"])) if n in (1, 2) else 1600 - int(r["S1"])]
for n, r in el.items()},
"pedestal": [int(el[1]["C3"]), int(el[1]["C6"])], "pad_margin": [200, 100],
"src": "ELEVATION TABLE; 평면 '1.750','6.500','1.750', S1; 상세 C3·C6, h2·50"}
G5 = E["G5"]
G5["el_x"] = {str(n): round(float(r["EL.'X'"]) * 1000) for n, r in el.items()}
G5["S"] = {str(r["no"]): r["S"] for r in rows}
G5["W"] = {str(r["no"]): r["W"] for r in rows}
G5["L"] = {str(r["no"]): r["L"] for r in rows}
G5["T"] = int(el[1]["h4"]) # sole plate thickness = h4 (P1: 31)
G5["h3"] = {str(n): int(r["h3"]) for n, r in el.items()}
G5["bearing_size"] = {str(n): [int(r["A"]), int(r["B"])] for n, r in el.items()}
(PIER / "params.json").write_text(json.dumps(P, ensure_ascii=False, indent=1), encoding="utf-8")
(PIER / "expected.json").write_text(json.dumps(E, ensure_ascii=False, indent=1), encoding="utf-8")
print("profile", profile)
print("coping", {k: c[k] for k in ("width_y", "plan_length", "top_length", "z_tip", "z_top_left", "z_top_right")})
print("lean", P["lean_concrete"]["size_x"], P["lean_concrete"]["size_y"], "footing", P["footing"]["size_x"], P["footing"]["size_y"],
P["footing"]["thickness"], "column", P["column"]["diameter"])
print("bearings", [(b["no"], b["centre"], b["seat_z"], b["el_x"], b["h1"], b["A"]) for b in bearings])
print("drawn pedestal centres", drawn)
print("sole plates", rows)
print("G5 T", G5["T"])
if __name__ == "__main__":
main()
+27
View File
@@ -0,0 +1,27 @@
"""Derive extract_r4.py (coping + bearing-seat rebar, G4) of the Ulsan P2 drawing from the P1 script (2026-10-15).
Basis: the P2 schedule rows C1~C15 / S1~S4 (values, counts) and the coping outline are identical to P1; only the levels differ (coping bottom
EL 68.716 vs 67.863 -> +853, tops EL 72.046 / 71.916, tip 70.416) and the table columns sit at other sheet x positions. All P1 drawn
coordinates are heights relative to the coping bottom, so they are reused with Z0 shifted by +853. Text sets of the coping rebar region were
compared (multiset diff P1 / P2): only column / footing / table / bearing values differ. Seat bars follow the P2 bearing table (centres, h1).
"""
from pathlib import Path
HERE = Path(__file__).resolve().parent
src = (HERE.parent / "울산P1판독" / "extract_r4.py").read_text(encoding="utf-8")
pairs = [
('"""R4 (coping rebar C1~C15, bearing-seat reinforcement S1~S4) reading of the Ulsan P1 drawing ->\ndata/산외교울산P1 params.coping_rebar',
'"""R4 (coping rebar C1~C15, bearing-seat reinforcement S1~S4) of the Ulsan P2 drawing (derived from the P1 script, see make_r4.py) ->\ndata/산외교울산P2 params.coping_rebar'),
('PIER = APP / "data" / "산외교울산P1"', 'PIER = APP / "data" / "산외교울산P2"'),
('Z0 = 99715.8 # z = drawing y + Z0 (front / side views)',
'Z0 = 99715.8 + 853 # P1 drawing y -> z, shifted by the P2 coping bottom (68716 - 67863)'),
('ZTL, ZTR, ZTIP = 71193.0, 71063.0, 69563.0', 'ZTL, ZTR, ZTIP = 72046.0, 71916.0, 70416.0'),
('if 111000 < x < 112500)', 'if 110000 < x < 111100)'),
('"src": "재료표 L·수량·범위; 정면·측면 작도 좌표 (z = y + 99,715.8); 단면 A-A/B-B; 받침부 보강 상세",',
'"src": "재료표 L·수량·범위; 울산P1 작도 높이(코핑 하면 기준) + 853 = 울산P2 코핑 하면 EL 68.716; 단면 A-A/B-B; 받침부 보강 상세",'),
]
for a, b in pairs:
assert src.count(a) == 1, a[:60]
src = src.replace(a, b)
(HERE / "extract_r4.py").write_text(src, encoding="utf-8")
print("written", HERE / "extract_r4.py")
+57
View File
@@ -0,0 +1,57 @@
"""Ulsan P1 rebar schedule (배근도(3) 철근재료표 SD400 / SD500) -> 판독/schedule.json.
The header row's column x positions (A, B, C, D, E, R, X1..X4, J, length, total) are taken from the drawing;
the first four columns (번호, 형식, 직경, 개수) are fixed left columns. Each data row (first cell = mark) is
mapped to the nearest header column. Ditto marks are resolved within a table. Values in m are kept as text.
"""
import json
import re
import sys
from pathlib import Path
HERE = Path(__file__).resolve().parent
T = json.loads((HERE.parent / "교각모델링앱" / "data" / "산외교울산P2" / "drawings" / "texts.json").read_text(encoding="utf-8"))
MARK = re.compile(r"^[A-Z]{1,2}\d{1,2}$")
def table(x0, x1, y_top, y_bot):
cells = [t for t in T if x0 <= t["x"] <= x1 and y_bot <= t["y"] <= y_top]
head = [t for t in cells if t["text"] in ("A", "B", "C", "D", "E", "R", "J")]
hy = max(round(t["y"]) for t in head) if head else None
cols = {t["text"]: t["x"] for t in head if abs(t["y"] - hy) < 100}
rows = {}
for t in cells:
rows.setdefault(round(t["y"] / 60), []).append(t)
out = []
for k in sorted(rows, reverse=True):
r = sorted(rows[k], key=lambda t: t["x"])
if not MARK.match(r[0]["text"]):
continue
rec = {"mark": r[0]["text"], "y": r[0]["y"]}
rest = r[1:]
for name, t in zip(("shape", "dia", "n"), rest[:3]):
rec[name] = t["text"]
for t in rest[3:]:
name = min(cols, key=lambda c: abs(cols[c] - t["x"]))
if abs(cols[name] - t["x"]) < 450 and name not in rec:
rec[name] = t["text"]
else:
rec.setdefault("_right", []).append((round(t["x"]), t["text"]))
out.append(rec)
return out, cols
if __name__ == "__main__":
import math
# locate the two tables by their titles
sd = {t["text"]: t for t in T if t["text"] in ("(SD400)", "(SD500)") and t["h"] < 300}
res = {}
for key, t in sd.items():
other = [o for o in sd.values() if o is not t and o["y"] < t["y"]]
y_bot = max(o["y"] for o in other) + 500 if other else t["y"] - 15000
recs, cols = table(t["x"] - 500, t["x"] + 22000, t["y"], y_bot)
res[key] = {"columns": cols, "rows": recs}
print(key, "columns", sorted(cols), "rows", len(recs))
for r in recs:
print(" ", {k: v for k, v in r.items() if k != "y"})
(HERE / "판독" / "schedule.json").write_text(json.dumps(res, ensure_ascii=False, indent=1), encoding="utf-8")
Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 KiB

+772
View File
@@ -0,0 +1,772 @@
{
"(SD400)": {
"columns": {
"A": 99850.4,
"B": 101088.4,
"C": 102326.4,
"D": 103564.4,
"E": 104802.4,
"R": 105913.4,
"J": 109317.4
},
"rows": [
{
"mark": "C1",
"y": -20474.6,
"shape": "IX",
"dia": "H32",
"n": "13",
"A": "9.259",
"B": "1.364",
"C": "1.369",
"_right": [
[
106538,
"100°"
],
[
107142,
"101°"
],
[
110516,
"13.552"
],
[
111669,
"176.176"
]
],
"J": "1.560"
},
{
"mark": "C2",
"y": -20744.6,
"shape": "IX",
"dia": "H32",
"n": "13",
"A": "9.297",
"B": "102",
"C": "102",
"_right": [
[
106538,
"100°"
],
[
107142,
"101°"
],
[
110516,
"11.061"
],
[
111669,
"143.793"
]
],
"J": "1.560"
},
{
"mark": "C3",
"y": -21014.6,
"shape": "IX",
"dia": "H32",
"n": "12",
"A": "9.259",
"B": "1.364",
"C": "1.369",
"_right": [
[
106538,
"100°"
],
[
107142,
"101°"
],
[
110516,
"13.552"
],
[
111669,
"162.624"
]
],
"J": "1.560"
},
{
"mark": "C4",
"y": -21284.6,
"shape": "IX",
"dia": "H32",
"n": "12",
"A": "9.297",
"B": "102",
"C": "102",
"_right": [
[
106538,
"100°"
],
[
107142,
"101°"
],
[
110516,
"11.061"
],
[
111669,
"132.732"
]
],
"J": "1.560"
},
{
"mark": "C5",
"y": -21554.6,
"shape": "IV",
"dia": "H32",
"n": "7",
"A": "650",
"B": "4.018",
"C": "2.457",
"D": "4.016",
"E": "650",
"_right": [
[
106538,
"104°"
],
[
107142,
"156°"
],
[
107748,
"156°"
],
[
108352,
"103°"
],
[
110516,
"12.731"
],
[
111754,
"89.117"
]
],
"J": "940"
},
{
"mark": "C6",
"y": -21824.6,
"shape": "IV",
"dia": "H32",
"n": "6",
"A": "650",
"B": "4.018",
"C": "2.457",
"D": "4.016",
"E": "650",
"_right": [
[
106538,
"104°"
],
[
107142,
"156°"
],
[
107748,
"156°"
],
[
108352,
"103°"
],
[
110516,
"12.731"
],
[
111754,
"76.386"
]
],
"J": "940"
},
{
"mark": "C7",
"y": -22364.6,
"shape": "II",
"dia": "H22",
"n": "51",
"A": "1.554",
"B": "3.106~1.656",
"C": "660",
"_right": [
[
110601,
"9.190"
],
[
111669,
"468.690"
]
]
},
{
"mark": "C8",
"y": -22634.6,
"shape": "II",
"dia": "H22",
"n": "21",
"A": "1.554",
"B": "3.184~3.054",
"C": "660",
"_right": [
[
110516,
"10.666"
],
[
111669,
"223.986"
]
]
},
{
"mark": "C9",
"y": -22904.6,
"shape": "II",
"dia": "H22",
"n": "51",
"A": "1.554",
"B": "2.977~1.526",
"C": "660",
"_right": [
[
110601,
"8.931"
],
[
111669,
"455.481"
]
]
},
{
"mark": "C10",
"y": -23174.6,
"shape": "III",
"dia": "H22",
"n": "7",
"A": "9.570~3.393",
"B": "3.054",
"J": "660",
"_right": [
[
110516,
"21.711"
],
[
111669,
"151.977"
]
]
},
{
"mark": "C11",
"y": -23444.6,
"shape": "VII",
"dia": "H22",
"n": "32",
"A": "9.560~3.383",
"B": "165",
"C": "140",
"R": "70",
"_right": [
[
110601,
"7.081"
],
[
111669,
"226.592"
]
]
},
{
"mark": "C12",
"y": -23714.6,
"shape": "XII",
"dia": "H22",
"n": "6",
"A": "8.974~8.666",
"B": "2.254",
"C": "628",
"R": "400",
"J": "660",
"_right": [
[
110516,
"27.300"
],
[
111669,
"163.800"
]
]
},
{
"mark": "C13",
"y": -23984.6,
"shape": "VIII",
"dia": "H22",
"n": "20",
"A": "9.764~9.456",
"B": "165",
"C": "140",
"R": "70",
"J": "660",
"_right": [
[
110516,
"10.880"
],
[
111669,
"217.600"
]
]
},
{
"mark": "C15",
"y": -24254.6,
"shape": "I",
"dia": "H22",
"n": "13",
"A": "4.500",
"B": "510",
"_right": [
[
110601,
"5.520"
],
[
111754,
"71.760"
]
]
},
{
"mark": "P5",
"y": -24794.6,
"shape": "I",
"dia": "H19",
"n": "76",
"A": "2.295",
"B": "150",
"_right": [
[
110601,
"2.595"
],
[
111669,
"197.220"
]
]
},
{
"mark": "C14",
"y": -25334.6,
"shape": "I",
"dia": "H16",
"n": "20",
"A": "3.000",
"B": "300",
"_right": [
[
110601,
"3.600"
],
[
111754,
"72.000"
]
]
},
{
"mark": "P2",
"y": -25604.6,
"shape": "VI",
"dia": "H16",
"n": "82",
"A": "3.610",
"B": "200",
"R": "1.149",
"J": "380",
"_right": [
[
110601,
"8.779"
],
[
111669,
"719.878"
]
]
},
{
"mark": "P3",
"y": -25874.6,
"shape": "V",
"dia": "H16",
"n": "12",
"A": "3.555",
"B": "55",
"R": "1.149",
"_right": [
[
110601,
"7.219"
],
[
111754,
"86.628"
]
]
},
{
"mark": "P4",
"y": -26144.6,
"shape": "X",
"dia": "H16",
"n": "31",
"A": "7.164",
"B": "55",
"R": "1.149",
"_right": [
[
110601,
"7.219"
],
[
111669,
"223.789"
]
]
},
{
"mark": "B7",
"y": -26684.6,
"shape": "XI",
"dia": "H13",
"n": "320",
"A": "2.285",
"B": "78",
"C": "52",
"_right": [
[
110601,
"2.415"
],
[
111669,
"772.800"
]
]
},
{
"mark": "S1",
"y": -26954.6,
"shape": "I",
"dia": "H13",
"n": "22",
"A": "1.200",
"B": "700",
"_right": [
[
110601,
"2.600"
],
[
111754,
"57.200"
]
]
},
{
"mark": "S2",
"y": -27224.6,
"shape": "I",
"dia": "H13",
"n": "22",
"A": "1.200",
"B": "700",
"_right": [
[
110601,
"2.600"
],
[
111754,
"57.200"
]
]
},
{
"mark": "S3",
"y": -27494.6,
"shape": "I",
"dia": "H13",
"n": "22",
"A": "1.200",
"B": "700",
"_right": [
[
110601,
"2.600"
],
[
111754,
"57.200"
]
]
},
{
"mark": "S4",
"y": -27764.6,
"shape": "I",
"dia": "H13",
"n": "22",
"A": "1.200",
"B": "700",
"_right": [
[
110601,
"2.600"
],
[
111754,
"57.200"
]
]
}
]
},
"(SD500)": {
"columns": {
"A": 99850.4,
"B": 101088.4,
"C": 102326.4,
"D": 103564.4,
"E": 104802.4,
"R": 105913.4,
"J": 109317.4
},
"rows": [
{
"mark": "P1",
"y": -30554.6,
"shape": "VI",
"dia": "H32",
"n": "68",
"A": "32.110",
"B": "534",
"C": "500",
"R": "340",
"_right": [
[
110516,
"33.145"
],
[
111584,
"2253.860"
]
]
},
{
"mark": "B2",
"y": -31094.6,
"shape": "IV",
"dia": "H25",
"n": "31",
"A": "7.300",
"B": "2.250",
"J": "1.160",
"_right": [
[
110516,
"12.960"
],
[
111669,
"401.760"
]
]
},
{
"mark": "B5",
"y": -31364.6,
"shape": "IV",
"dia": "H25",
"n": "31",
"A": "7.600",
"B": "2.250",
"J": "1.160",
"_right": [
[
110516,
"13.260"
],
[
111669,
"411.060"
]
]
},
{
"mark": "B6",
"y": -31634.6,
"shape": "I",
"dia": "H25",
"n": "28",
"A": "7.600",
"_right": [
[
110601,
"7.600"
],
[
111669,
"212.800"
]
]
},
{
"mark": "B3",
"y": -32174.6,
"shape": "I",
"dia": "H22",
"n": "30",
"A": "7.300",
"_right": [
[
110601,
"7.300"
],
[
111669,
"219.000"
]
]
},
{
"mark": "B1",
"y": -32714.6,
"shape": "I",
"dia": "H19",
"n": "31",
"A": "7.300",
"_right": [
[
110601,
"7.300"
],
[
111669,
"226.300"
]
]
},
{
"mark": "B4",
"y": -32984.6,
"shape": "I",
"dia": "H19",
"n": "31",
"A": "7.600",
"_right": [
[
110601,
"7.600"
],
[
111669,
"235.600"
]
]
},
{
"mark": "B8",
"y": -33524.6,
"shape": "V",
"dia": "H16",
"n": "7",
"A": "7.600",
"B": "7.355",
"J": "570",
"_right": [
[
110516,
"33.330"
],
[
111669,
"233.310"
]
]
},
{
"mark": "B9",
"y": -33794.6,
"shape": "III",
"dia": "H16",
"n": "7",
"A": "7.600",
"B": "100",
"_right": [
[
110601,
"7.800"
],
[
111754,
"54.600"
]
]
},
{
"mark": "B10",
"y": -34064.6,
"shape": "III",
"dia": "H16",
"n": "8",
"A": "7.300",
"B": "100",
"_right": [
[
110601,
"7.500"
],
[
111754,
"60.000"
]
]
}
]
}
}
@@ -0,0 +1,36 @@
(SD400) columns ['A', 'B', 'C', 'D', 'E', 'J', 'R'] rows 24
{'mark': 'C1', 'shape': 'IX', 'dia': 'H32', 'n': '13', 'A': '9.259', 'B': '1.364', 'C': '1.369', '_right': [(106538, '100°'), (107142, '101°'), (110516, '13.552'), (111669, '176.176')], 'J': '1.560'}
{'mark': 'C2', 'shape': 'IX', 'dia': 'H32', 'n': '13', 'A': '9.297', 'B': '102', 'C': '102', '_right': [(106538, '100°'), (107142, '101°'), (110516, '11.061'), (111669, '143.793')], 'J': '1.560'}
{'mark': 'C3', 'shape': 'IX', 'dia': 'H32', 'n': '12', 'A': '9.259', 'B': '1.364', 'C': '1.369', '_right': [(106538, '100°'), (107142, '101°'), (110516, '13.552'), (111669, '162.624')], 'J': '1.560'}
{'mark': 'C4', 'shape': 'IX', 'dia': 'H32', 'n': '12', 'A': '9.297', 'B': '102', 'C': '102', '_right': [(106538, '100°'), (107142, '101°'), (110516, '11.061'), (111669, '132.732')], 'J': '1.560'}
{'mark': 'C5', 'shape': 'IV', 'dia': 'H32', 'n': '7', 'A': '650', 'B': '4.018', 'C': '2.457', 'D': '4.016', 'E': '650', '_right': [(106538, '104°'), (107142, '156°'), (107748, '156°'), (108352, '103°'), (110516, '12.731'), (111754, '89.117')], 'J': '940'}
{'mark': 'C6', 'shape': 'IV', 'dia': 'H32', 'n': '6', 'A': '650', 'B': '4.018', 'C': '2.457', 'D': '4.016', 'E': '650', '_right': [(106538, '104°'), (107142, '156°'), (107748, '156°'), (108352, '103°'), (110516, '12.731'), (111754, '76.386')], 'J': '940'}
{'mark': 'C7', 'shape': 'II', 'dia': 'H22', 'n': '51', 'A': '1.554', 'B': '3.106~1.656', 'C': '660', '_right': [(110601, '9.190'), (111669, '468.690')]}
{'mark': 'C8', 'shape': 'II', 'dia': 'H22', 'n': '21', 'A': '1.554', 'B': '3.184~3.054', 'C': '660', '_right': [(110516, '10.666'), (111669, '223.986')]}
{'mark': 'C9', 'shape': 'II', 'dia': 'H22', 'n': '51', 'A': '1.554', 'B': '2.977~1.526', 'C': '660', '_right': [(110601, '8.931'), (111669, '455.481')]}
{'mark': 'C10', 'shape': 'III', 'dia': 'H22', 'n': '7', 'A': '9.570~3.393', 'B': '3.054', 'J': '660', '_right': [(110516, '21.711'), (111669, '151.977')]}
{'mark': 'C11', 'shape': 'VII', 'dia': 'H22', 'n': '32', 'A': '9.560~3.383', 'B': '165', 'C': '140', 'R': '70', '_right': [(110601, '7.081'), (111669, '226.592')]}
{'mark': 'C12', 'shape': 'XII', 'dia': 'H22', 'n': '6', 'A': '8.974~8.666', 'B': '2.254', 'C': '628', 'R': '400', 'J': '660', '_right': [(110516, '27.300'), (111669, '163.800')]}
{'mark': 'C13', 'shape': 'VIII', 'dia': 'H22', 'n': '20', 'A': '9.764~9.456', 'B': '165', 'C': '140', 'R': '70', 'J': '660', '_right': [(110516, '10.880'), (111669, '217.600')]}
{'mark': 'C15', 'shape': 'I', 'dia': 'H22', 'n': '13', 'A': '4.500', 'B': '510', '_right': [(110601, '5.520'), (111754, '71.760')]}
{'mark': 'P5', 'shape': 'I', 'dia': 'H19', 'n': '76', 'A': '2.295', 'B': '150', '_right': [(110601, '2.595'), (111669, '197.220')]}
{'mark': 'C14', 'shape': 'I', 'dia': 'H16', 'n': '20', 'A': '3.000', 'B': '300', '_right': [(110601, '3.600'), (111754, '72.000')]}
{'mark': 'P2', 'shape': 'VI', 'dia': 'H16', 'n': '82', 'A': '3.610', 'B': '200', 'R': '1.149', 'J': '380', '_right': [(110601, '8.779'), (111669, '719.878')]}
{'mark': 'P3', 'shape': 'V', 'dia': 'H16', 'n': '12', 'A': '3.555', 'B': '55', 'R': '1.149', '_right': [(110601, '7.219'), (111754, '86.628')]}
{'mark': 'P4', 'shape': 'X', 'dia': 'H16', 'n': '31', 'A': '7.164', 'B': '55', 'R': '1.149', '_right': [(110601, '7.219'), (111669, '223.789')]}
{'mark': 'B7', 'shape': 'XI', 'dia': 'H13', 'n': '320', 'A': '2.285', 'B': '78', 'C': '52', '_right': [(110601, '2.415'), (111669, '772.800')]}
{'mark': 'S1', 'shape': 'I', 'dia': 'H13', 'n': '22', 'A': '1.200', 'B': '700', '_right': [(110601, '2.600'), (111754, '57.200')]}
{'mark': 'S2', 'shape': 'I', 'dia': 'H13', 'n': '22', 'A': '1.200', 'B': '700', '_right': [(110601, '2.600'), (111754, '57.200')]}
{'mark': 'S3', 'shape': 'I', 'dia': 'H13', 'n': '22', 'A': '1.200', 'B': '700', '_right': [(110601, '2.600'), (111754, '57.200')]}
{'mark': 'S4', 'shape': 'I', 'dia': 'H13', 'n': '22', 'A': '1.200', 'B': '700', '_right': [(110601, '2.600'), (111754, '57.200')]}
(SD500) columns ['A', 'B', 'C', 'D', 'E', 'J', 'R'] rows 10
{'mark': 'P1', 'shape': 'VI', 'dia': 'H32', 'n': '68', 'A': '32.110', 'B': '534', 'C': '500', 'R': '340', '_right': [(110516, '33.145'), (111584, '2253.860')]}
{'mark': 'B2', 'shape': 'IV', 'dia': 'H25', 'n': '31', 'A': '7.300', 'B': '2.250', 'J': '1.160', '_right': [(110516, '12.960'), (111669, '401.760')]}
{'mark': 'B5', 'shape': 'IV', 'dia': 'H25', 'n': '31', 'A': '7.600', 'B': '2.250', 'J': '1.160', '_right': [(110516, '13.260'), (111669, '411.060')]}
{'mark': 'B6', 'shape': 'I', 'dia': 'H25', 'n': '28', 'A': '7.600', '_right': [(110601, '7.600'), (111669, '212.800')]}
{'mark': 'B3', 'shape': 'I', 'dia': 'H22', 'n': '30', 'A': '7.300', '_right': [(110601, '7.300'), (111669, '219.000')]}
{'mark': 'B1', 'shape': 'I', 'dia': 'H19', 'n': '31', 'A': '7.300', '_right': [(110601, '7.300'), (111669, '226.300')]}
{'mark': 'B4', 'shape': 'I', 'dia': 'H19', 'n': '31', 'A': '7.600', '_right': [(110601, '7.600'), (111669, '235.600')]}
{'mark': 'B8', 'shape': 'V', 'dia': 'H16', 'n': '7', 'A': '7.600', 'B': '7.355', 'J': '570', '_right': [(110516, '33.330'), (111669, '233.310')]}
{'mark': 'B9', 'shape': 'III', 'dia': 'H16', 'n': '7', 'A': '7.600', 'B': '100', '_right': [(110601, '7.800'), (111754, '54.600')]}
{'mark': 'B10', 'shape': 'III', 'dia': 'H16', 'n': '8', 'A': '7.300', 'B': '100', '_right': [(110601, '7.500'), (111754, '60.000')]}