Files
오문성andClaude Haiku 5.5 9068b6c7f2 공개용 정리본: 1차 제출본(도면 좌표·과업명 제거)으로 새 이력 시작 (SPEC 34장)
- 정리는 코치 세션이 저장소 밖 도구로 수행, 하이쿠 세션이 커밋.
- 옛 81커밋은 로컬에만 보존. 기록은 evidence/gitlog_v1.txt.
- F13 기준 변경: .gitignore 에 private/*.dwg·private/full.dxf 제외 (SPEC 34장 끝 문장).
- check.sh 결과: 결과: PASS 151 / FAIL 0 / SKIP 3 (검사 범위 F01~F46)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-27 14:27:50 +09:00

179 lines
6.1 KiB
Python

#!/usr/bin/env python3
# select_views.py — DXF 에서 이 과제가 쓰는 그림 상자 안의 객체만 남긴다 (표준 라이브러리만, SPEC 28장)
# 사용: python3 tools/select_views.py <전체.dxf> <views.json> <출력.dxf>
# 상자·뺄 레이어·뺄 블록 이름은 views.json 에서만 읽는다. 이 파일에는 도면 좌표·레이어 이름이 없다.
import json
import sys
COORD_X = ("10", "11")
COORD_Y = ("20", "21")
def read_pairs(path):
# DXF 는 (코드 줄, 값 줄) 쌍이다. 원본 글자를 그대로 두려고 줄 끝만 떼고 저장한다.
with open(path, encoding="utf-8", errors="surrogateescape", newline="") as f:
lines = f.read().split("\n")
lines = [ln[:-1] if ln.endswith("\r") else ln for ln in lines]
if lines and lines[-1] == "":
lines.pop()
return [(lines[i], lines[i + 1]) for i in range(0, len(lines) - 1, 2)]
def code_of(pair):
return pair[0].strip()
def value_of(pair):
return pair[1].strip()
def find_entities(pairs):
# ENTITIES 구역의 시작(SECTION 다음 줄이 ENTITIES)과 끝(ENDSEC) 위치
for s in range(len(pairs) - 1):
if code_of(pairs[s]) == "0" and value_of(pairs[s]) == "SECTION" and value_of(pairs[s + 1]) == "ENTITIES":
for e in range(s + 2, len(pairs)):
if code_of(pairs[e]) == "0" and value_of(pairs[e]) == "ENDSEC":
return s, e
raise SystemExit("ENTITIES 구역을 찾지 못했다")
def group_entities(body):
# 객체 묶음: 보통은 0 줄 하나에서 다음 0 줄 전까지.
# POLYLINE 은 SEQEND 까지. 코드 66 이 1 인 INSERT 는 ATTRIB…SEQEND 까지.
groups = []
i = 0
while i < len(body):
start = i
i += 1
while i < len(body) and code_of(body[i]) != "0":
i += 1
head_type = value_of(body[start])
if head_type == "POLYLINE" or (head_type == "INSERT" and has_attrib_flag(body[start:i])):
while i < len(body) and not (code_of(body[i]) == "0" and value_of(body[i]) == "SEQEND"):
i += 1
if i < len(body):
i += 1 # SEQEND 자체도 묶음에 넣는다
# 다음 객체 머리(0 줄)까지 같은 묶음
while i < len(body) and code_of(body[i]) != "0":
i += 1
groups.append(body[start:i])
return groups
def has_attrib_flag(head_pairs):
# INSERT 머리(첫 0 줄 다음) 안의 코드 66 값이 1 이면 속성이 뒤따른다
for p in head_pairs[1:]:
if code_of(p) == "66":
return value_of(p) == "1"
return False
def head_flag(group, code):
# 묶음 머리 객체의 코드 값 (ATTRIB 등 뒤 객체 값은 보지 않는다)
for p in group[1:]:
if code_of(p) == "0":
break
if code_of(p) == code:
return value_of(p)
return None
def coords(group):
# 좌표: 코드 10·11 의 X 와 그 뒤 20·21 의 Y. POLYLINE 은 머리의 기준점을 빼고 VERTEX 부터.
start = 0
if value_of(group[0]) == "POLYLINE":
for k, p in enumerate(group):
if code_of(p) == "0" and value_of(p) == "VERTEX":
start = k
break
else:
return []
pts = []
body = group[start:]
for k, p in enumerate(body):
if code_of(p) in COORD_X and k + 1 < len(body) and code_of(body[k + 1]) in COORD_Y:
try:
pts.append((float(value_of(p)), float(value_of(body[k + 1]))))
except ValueError:
pass
# CIRCLE·ARC: 중심 ± 반지름(코드 40)
if value_of(group[0]) in ("CIRCLE", "ARC") and pts:
r = head_flag(group, "40")
try:
r = float(r)
except (TypeError, ValueError):
r = 0.0
cx, cy = pts[0]
pts += [(cx - r, cy - r), (cx + r, cy + r)]
return pts
def dropped(group, cfg):
head = group[0]
if head_flag(group, "67") == "1":
return True # 배치 공간
if head_flag(group, "8") in cfg["drop_layers"]:
return True
if value_of(head) == "INSERT" and cfg["drop_block_word"] in (head_flag(group, "2") or ""):
return True
return False
def inside(pts, box):
x0, y0, x1, y1 = box
return all(x0 <= x <= x1 and y0 <= y <= y1 for x, y in pts)
def main(argv):
if len(argv) != 4:
print("사용법: python3 tools/select_views.py <전체.dxf> <views.json> <출력.dxf>")
return 2
with open(argv[2], encoding="utf-8") as f:
cfg = json.load(f)
boxes = [b["range"] for b in cfg["boxes"]]
pairs = read_pairs(argv[1])
s, e = find_entities(pairs)
groups = group_entities(pairs[s + 2:e])
kept, allpts = [], []
for g in groups:
if dropped(g, cfg):
continue
pts = coords(g)
if not pts:
continue
if any(inside(pts, b) for b in boxes):
kept.append(g)
allpts += pts
# 헤더의 $EXTMIN·$EXTMAX 를 남긴 좌표의 최소·최대로 바꾼다 (헤더 안 10/20 줄만)
head = pairs[:s]
if allpts:
xs = [p[0] for p in allpts]
ys = [p[1] for p in allpts]
new_min, new_max = (min(xs), min(ys)), (max(xs), max(ys))
for k in range(len(head) - 1):
name = value_of(head[k]) if code_of(head[k]) == "9" else None
if name not in ("$EXTMIN", "$EXTMAX"):
continue
target = new_min if name == "$EXTMIN" else new_max
j = k + 1
while j + 1 < len(head) and code_of(head[j]) != "9":
if code_of(head[j]) == "10":
head[j] = (head[j][0], repr(target[0]))
elif code_of(head[j]) == "20":
head[j] = (head[j][0], repr(target[1]))
j += 1
out = head + pairs[s:s + 2] + [p for g in kept for p in g] + pairs[e:]
with open(argv[3], "w", encoding="utf-8", errors="surrogateescape", newline="") as f:
for c, v in out:
f.write(c + "\r\n" + v + "\r\n")
print(f"완료: {argv[3]} (객체 {len(kept)}개 / 전체 {len(groups)}개)")
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))