| ``` | |
| import ezdxf | |
| import matplotlib.pyplot as plt | |
| from matplotlib.patches import Arc | |
| import numpy as np | |
| import math | |
| # ============== ์ฌ์ฉ์ ์ ๋ ฅ ============== | |
| dxf_path = "plan.dxf" | |
| # ํ์ด๋ผ์ดํธํ ๋ ธ๋ ๊ทธ๋ฃน (์ํ๋ ๋งํผ ์ถ๊ฐ) | |
| # points: ์ฌ์ฉ์๊ฐ ์ง์ ํ ์ขํ(๋๋ต์น์ฌ๋ ์ค๋ ๋จ), label์ ์ต์ | |
| selected_groups = { | |
| "CHECK": { | |
| "color": "red", | |
| "points": [(100.0, 200.0), (150.2, 80.7)], # ์์ ์ขํ | |
| "labels": ["N1", "N2"], # ์์ผ๋ฉด ์๋ ๋ฒํธ | |
| }, | |
| "MONITOR": { | |
| "color": "orange", | |
| "points": [(300.0, 210.0)], | |
| "labels": None, | |
| }, | |
| } | |
| snap_tol = 1.0 # ์ค๋ ํ์ฉ์ค์ฐจ(๋๋ฉด ๋จ์). ๋๋ฉด ์ค์ผ์ผ์ ๋ง์ถฐ ์กฐ์ | |
| marker_size = 8 # ๋ง์ปค ํฝ์ ํฌ๊ธฐ | |
| label_fontsize = 6 # ๋ผ๋ฒจ ํฐํธ ํฌ๊ธฐ | |
| # ============== DXF ๋ก๋ ============== | |
| doc = ezdxf.readfile(dxf_path) | |
| msp = doc.modelspace() | |
| fig, ax = plt.subplots(figsize=(10, 10)) | |
| # --------------------------- | |
| # ๊ณตํต ์ ํธ | |
| # --------------------------- | |
| def plot_segments(ax, points, closed=False, lw=0.6, color="black"): | |
| if len(points) < 2: | |
| return | |
| xs, ys = zip(*points) | |
| ax.plot(xs, ys, linewidth=lw, color=color) | |
| if closed and (points[0] != points[-1]): | |
| ax.plot([points[-1][0], points[0][0]], | |
| [points[-1][1], points[0][1]], linewidth=lw, color=color) | |
| def plot_lwpolyline(ax, e): | |
| """LWPOLYLINE์ bulge(ํธ)๋ฅผ ๊ทผ์ฌํ์ฌ ๊ทธ๋ฆผ.""" | |
| pts = list(e.get_points("xyb")) | |
| if not pts: | |
| return | |
| approx = [] | |
| for i in range(len(pts) - 1 + int(e.closed)): | |
| x1, y1, b1 = pts[i % len(pts)] | |
| x2, y2, _ = pts[(i+1) % len(pts)] | |
| p1 = np.array([x1, y1]) | |
| p2 = np.array([x2, y2]) | |
| if abs(b1) < 1e-12: | |
| approx += [tuple(p1), tuple(p2)] | |
| else: | |
| delta = 4 * math.atan(b1) # ์ค์ฌ๊ฐ | |
| chord = p2 - p1 | |
| L = np.linalg.norm(chord) | |
| if L < 1e-12: | |
| continue | |
| R = (L/2) / abs(math.sin(delta/2)) | |
| mid = (p1 + p2) / 2 | |
| n = np.array([-(chord[1]), chord[0]]) / (L + 1e-12) | |
| h = R * math.cos(delta/2) | |
| center = mid + np.sign(b1) * h * n | |
| a1 = math.atan2(p1[1]-center[1], p1[0]-center[0]) | |
| a2 = math.atan2(p2[1]-center[1], p2[0]-center[0]) | |
| def angle_range(a_start, a_end, ccw=True, steps=32): | |
| if ccw: | |
| if a_end <= a_start: | |
| a_end += 2*math.pi | |
| return np.linspace(a_start, a_end, steps) | |
| else: | |
| if a_end >= a_start: | |
| a_end -= 2*math.pi | |
| return np.linspace(a_start, a_end, steps) | |
| ccw = (b1 > 0) | |
| angles = angle_range(a1, a2, ccw=ccw, steps=max(16, int(abs(delta)*16))) | |
| for t in angles: | |
| approx.append((center[0] + R*math.cos(t), center[1] + R*math.sin(t))) | |
| cleaned = [] | |
| for p in approx: | |
| if not cleaned or (abs(cleaned[-1][0]-p[0])>1e-9 or abs(cleaned[-1][1]-p[1])>1e-9): | |
| cleaned.append(p) | |
| plot_segments(ax, cleaned, closed=e.closed, lw=0.6) | |
| def draw_basic_entity(ax, ent): | |
| """INSERT ์ ๊ฐ๋ ๊ฐ์ ์ํฐํฐ ํฌํจ, ๊ฐ๋ณ ์ํฐํฐ๋ฅผ ๊ทธ๋ฆผ.""" | |
| t = ent.dxftype() | |
| if t == "LINE": | |
| x = [ent.dxf.start.x, ent.dxf.end.x] | |
| y = [ent.dxf.start.y, ent.dxf.end.y] | |
| ax.plot(x, y, linewidth=0.6, color="black") | |
| elif t == "LWPOLYLINE": | |
| plot_lwpolyline(ax, ent) | |
| elif t == "POLYLINE": | |
| pts = [(v.dxf.location.x, v.dxf.location.y) for v in ent.vertices] | |
| plot_segments(ax, pts, closed=getattr(ent, "is_closed", False), lw=0.6) | |
| elif t == "ARC": | |
| c = ent.dxf.center; r = ent.dxf.radius | |
| arc = Arc((c.x, c.y), width=2*r, height=2*r, angle=0, | |
| theta1=ent.dxf.start_angle, theta2=ent.dxf.end_angle, | |
| linewidth=0.6, color="black") | |
| ax.add_patch(arc) | |
| elif t == "CIRCLE": | |
| c = ent.dxf.center; r = ent.dxf.radius | |
| ax.add_patch(plt.Circle((c.x, c.y), r, fill=False, linewidth=0.6, color="black")) | |
| elif t == "ELLIPSE": | |
| center = np.array([ent.dxf.center.x, ent.dxf.center.y]) | |
| major = np.array([ent.dxf.major_axis.x, ent.dxf.major_axis.y]) | |
| ratio = ent.dxf.ratio | |
| t0 = ent.dxf.start_param; t1 = ent.dxf.end_param | |
| u = major | |
| v = np.array([-major[1], major[0]]) | |
| v = v / (np.linalg.norm(v) + 1e-12) * (np.linalg.norm(major) * ratio) | |
| ts = np.linspace(t0, t1, 200) | |
| xs = center[0] + u[0]*np.cos(ts) + v[0]*np.sin(ts) | |
| ys = center[1] + u[1]*np.cos(ts) + v[1]*np.sin(ts) | |
| ax.plot(xs, ys, linewidth=0.6, color="black") | |
| elif t == "SPLINE": | |
| pts = ent.approximate(segments=200) | |
| xs, ys = zip(*[(p[0], p[1]) for p in pts]) | |
| ax.plot(xs, ys, linewidth=0.6, color="black") | |
| elif t == "TEXT": | |
| ins = ent.dxf.insert; text = ent.dxf.text | |
| height = ent.dxf.height if ent.dxf.height else 2.5 | |
| rot = ent.dxf.rotation if ent.dxf.hasattr("rotation") else 0.0 | |
| ax.text(ins.x, ins.y, text, fontsize=height, rotation=rot, | |
| rotation_mode="anchor", ha="left", va="baseline", color="black") | |
| elif t in ("MTEXT", "ATTRIB"): | |
| ins = ent.dxf.insert | |
| text = ent.plain_text() if t == "MTEXT" else ent.dxf.text | |
| rot = ent.dxf.rotation if ent.dxf.hasattr("rotation") else 0.0 | |
| h = getattr(ent.dxf, "char_height", None) or getattr(ent.dxf, "height", None) or 2.5 | |
| ax.text(ins.x, ins.y, text, fontsize=h, rotation=rot, | |
| rotation_mode="anchor", ha="left", va="top", color="black") | |
| elif t == "HATCH": | |
| for path in ent.paths: | |
| if path.PATH_TYPE_EDGE: | |
| pts = [] | |
| for edge in path.edges: | |
| typ = edge.EDGE_TYPE | |
| if typ == "LineEdge": | |
| pts += [(edge.start[0], edge.start[1]), (edge.end[0], edge.end[1])] | |
| elif typ == "ArcEdge": | |
| cx, cy = edge.center; r = edge.radius | |
| a0 = math.radians(edge.start_angle); a1 = math.radians(edge.end_angle) | |
| ts = np.linspace(a0, a1, 50) | |
| pts += [(cx + r*np.cos(t), cy + r*np.sin(t)) for t in ts] | |
| elif typ == "EllipseEdge": | |
| (cx, cy) = edge.center | |
| major = np.array(edge.major_axis); ratio = edge.ratio | |
| t0, t1 = edge.start_param, edge.end_param | |
| u = major; v = np.array([-major[1], major[0]]) | |
| v = v / (np.linalg.norm(v)+1e-12) * (np.linalg.norm(major)*ratio) | |
| ts = np.linspace(t0, t1, 100) | |
| pts += [(cx + u[0]*np.cos(t) + v[0]*np.sin(t), | |
| cy + u[1]*np.cos(t) + v[1]*np.sin(t)) for t in ts] | |
| elif typ == "SplineEdge": | |
| ap = edge.spline.approximate(segments=100) | |
| pts += [(p[0], p[1]) for p in ap] | |
| if len(pts) >= 2: | |
| plot_segments(ax, pts, lw=0.4) | |
| elif path.PATH_TYPE_POLYLINE: | |
| pts = [(v[0], v[1]) for v in path.vertices] | |
| plot_segments(ax, pts, lw=0.4) | |
| # --------------------------- | |
| # 1) ๊ธฐ๋ณธ ์ํฐํฐ ๊ทธ๋ฆฌ๊ธฐ(INSERT ์ ์ธ) | |
| # --------------------------- | |
| for e in msp: | |
| if e.dxftype() == "INSERT": | |
| continue | |
| draw_basic_entity(ax, e) | |
| # --------------------------- | |
| # 2) ๋ธ๋ก(INSERT) ์ ๊ฐ + DIMENSION | |
| # --------------------------- | |
| for br in msp.query("INSERT"): | |
| try: | |
| for ve in br.virtual_entities(): | |
| draw_basic_entity(ax, ve) | |
| except Exception: | |
| continue | |
| for dim in msp.query("DIMENSION"): | |
| try: | |
| dim.render() | |
| for ve in dim.virtual_entities(): | |
| draw_basic_entity(ax, ve) | |
| except Exception: | |
| continue | |
| # --------------------------- | |
| # 3) ๋ ธ๋ ํ๋ณด ์ถ์ถ (๋์ /๋ฒํ ์ค ์ค์ฌ) | |
| # --------------------------- | |
| node_candidates = [] | |
| def add_node(p): | |
| node_candidates.append((float(p[0]), float(p[1]))) | |
| # LINE ๋์ | |
| for e in msp.query("LINE"): | |
| add_node((e.dxf.start.x, e.dxf.start.y)) | |
| add_node((e.dxf.end.x, e.dxf.end.y)) | |
| # LWPOLYLINE: ๋ฒํ ์ค ์ขํ(ํธ ์ค๊ฐ์ ๊น์ง ๋ค ๋ฃ์ผ๋ฉด ๋๋ฌด ๋ง์์ ธ์ ๋ฒํ ์ค๋ง) | |
| for e in msp.query("LWPOLYLINE"): | |
| for (x, y, *_) in e.get_points("xyb"): | |
| add_node((x, y)) | |
| # POLYLINE: ๋ฒํ ์ค | |
| for e in msp.query("POLYLINE"): | |
| for v in e.vertices: | |
| add_node((v.dxf.location.x, v.dxf.location.y)) | |
| # ARC/CIRCLE ์ค์ฌ์ ๋ ธ๋๋ก ์ฐ๊ณ ์ถ๋ค๋ฉด ์๋ ์ฃผ์ ํด์ | |
| # for e in msp.query("ARC"): | |
| # add_node((e.dxf.center.x, e.dxf.center.y)) | |
| # for e in msp.query("CIRCLE"): | |
| # add_node((e.dxf.center.x, e.dxf.center.y)) | |
| # ์ค๋ณต ์ ๊ฑฐ(๊ฒฉ์ ์ค๋ ) | |
| def snap_key(p, tol=1e-6): | |
| return (round(p[0]/tol), round(p[1]/tol)) | |
| uniq = {} | |
| for p in node_candidates: | |
| k = snap_key(p, 1e-6) | |
| if k not in uniq: | |
| uniq[k] = p | |
| node_candidates = list(uniq.values()) | |
| # --------------------------- | |
| # 4) ์ ํ ๋ ธ๋ ์ค๋ & ํ์ด๋ผ์ดํธ | |
| # --------------------------- | |
| def find_nearest_node(pt, candidates, tol): | |
| """pt์ ๊ฐ์ฅ ๊ฐ๊น์ด ํ๋ณด๋ฅผ ์ฐพ๊ณ , ๊ฑฐ๋ฆฌ๊ฐ tol๋ณด๋ค ํฌ๋ฉด None.""" | |
| px, py = pt | |
| best = None; best_d2 = None | |
| for cx, cy in candidates: | |
| d2 = (cx - px)**2 + (cy - py)**2 | |
| if (best_d2 is None) or (d2 < best_d2): | |
| best_d2 = d2; best = (cx, cy) | |
| if best is None: | |
| return None | |
| dist = math.sqrt(best_d2) | |
| return best if dist <= tol else None | |
| def draw_marker(ax, x, y, color="red", size=8, zorder=10): | |
| # ํ๋ฉด ํฝ์ ๊ณ ์ ํฌ๊ธฐ ๋ง์ปค(๋๋ฉด ์ถ์ฒ ๋ฌด๊ด) | |
| ax.scatter([x], [y], s=size**2, c=color, marker='o', zorder=zorder, linewidths=0.5, edgecolors="black") | |
| # ๊ทธ๋ฃน๋ณ๋ก ์ค๋ /ํ์ | |
| legend_handles = [] | |
| for gname, cfg in selected_groups.items(): | |
| color = cfg.get("color", "red") | |
| pts = cfg.get("points", []) | |
| labels= cfg.get("labels", None) | |
| placed = [] | |
| for i, pt in enumerate(pts): | |
| snapped = find_nearest_node(pt, node_candidates, snap_tol) | |
| if snapped is None: | |
| # ๊ฐ๊น์ด ๋ ธ๋๊ฐ ์์ผ๋ฉด ์๋ ๋๋ต ์ขํ์ ๋ง์ปค(์ ๋ค๋ฅด๊ฒ ํ์) | |
| draw_marker(ax, pt[0], pt[1], color="gray", size=marker_size, zorder=12) | |
| if labels: | |
| ax.text(pt[0], pt[1], labels[i], fontsize=label_fontsize, color="gray", | |
| ha="left", va="bottom", zorder=12) | |
| continue | |
| x, y = snapped | |
| draw_marker(ax, x, y, color=color, size=marker_size, zorder=13) | |
| if labels: | |
| ax.text(x, y, labels[i], fontsize=label_fontsize, color=color, | |
| ha="left", va="bottom", zorder=13) | |
| placed.append((x, y)) | |
| # ๋ฒ๋ก์ฉ ๋๋ฏธ(์ ํ์ฌํญ) | |
| lh = ax.scatter([], [], s=marker_size**2, c=color, marker='o', edgecolors="black", label=gname) | |
| legend_handles.append(lh) | |
| if legend_handles: | |
| ax.legend(loc="upper right", fontsize=8, frameon=True) | |
| # --------------------------- | |
| # 5) ๋ณด๊ธฐ/์ ์ฅ | |
| # --------------------------- | |
| ax.set_aspect("equal") | |
| ax.axis("off") | |
| # ๋๋ฉด ์ ์ฒด extents ์๋ ํฌ๋กญ(์ฌ๋ฐฑ ํฌํจ) | |
| try: | |
| # ezdxf์ extents ๊ณ์ฐ์ ์ฐ๊ณ ์ถ๋ค๋ฉด: | |
| # from ezdxf.addons.drawing import layout | |
| # ext = layout.Layout(msp).bbox() # ๋ฒ์ ์ ๋ฐ๋ผ ๋ค๋ฅผ ์ ์์ | |
| # ์ฌ๊ธฐ์๋ ์ฐํฌ๋ก ์ถ์ : | |
| xs, ys = [], [] | |
| for (x, y) in node_candidates: | |
| xs.append(x); ys.append(y) | |
| if xs and ys: | |
| margin = 0.05 # 5% ์ฌ๋ฐฑ | |
| xmin, xmax = min(xs), max(xs) | |
| ymin, ymax = min(ys), max(ys) | |
| dx = xmax - xmin; dy = ymax - ymin | |
| ax.set_xlim(xmin - dx*margin, xmax + dx*margin) | |
| ax.set_ylim(ymin - dy*margin, ymax + dy*margin) | |
| except Exception: | |
| pass | |
| plt.savefig("plan_with_nodes.png", dpi=300, bbox_inches="tight", pad_inches=0.02) | |
| plt.savefig("plan_with_nodes.pdf", bbox_inches="tight", pad_inches=0.02) | |
| plt.close() | |
| ``` |