#!/usr/bin/env python3 # select_views.py — DXF 에서 이 과제가 쓰는 그림 상자 안의 객체만 남긴다 (표준 라이브러리만, SPEC 28장) # 사용: python3 tools/select_views.py <전체.dxf> <출력.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> <출력.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))