Doramong commited on
Commit
589d996
·
verified ·
1 Parent(s): 25d3c96

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +195 -119
README.md CHANGED
@@ -10,131 +10,207 @@ msp = doc.modelspace()
10
 
11
  fig, ax = plt.subplots(figsize=(8, 8))
12
 
13
- # 1) LINE
14
- for e in msp.query("LINE"):
15
- x = [e.dxf.start.x, e.dxf.end.x]
16
- y = [e.dxf.start.y, e.dxf.end.y]
17
- ax.plot(x, y, linewidth=0.6, color="black")
18
-
19
- # 2) LWPOLYLINE / POLYLINE
20
- def plot_poly(points, closed, lw=0.6):
21
  xs, ys = zip(*points)
22
  ax.plot(xs, ys, linewidth=lw, color="black")
23
  if closed and (points[0] != points[-1]):
24
  ax.plot([points[-1][0], points[0][0]],
25
  [points[-1][1], points[0][1]], linewidth=lw, color="black")
26
 
27
- for e in msp.query("LWPOLYLINE"):
28
- pts = [(p[0], p[1]) for p in e.get_points()] # (x, y, [bulge...])
29
- plot_poly(pts, e.closed)
30
-
31
- for e in msp.query("POLYLINE"):
32
- pts = [(v.dxf.location.x, v.dxf.location.y) for v in e.vertices]
33
- plot_poly(pts, e.is_closed)
34
-
35
- # 3) ARC (중심, 반경, 시작각, 끝각)
36
- for e in msp.query("ARC"):
37
- c = e.dxf.center
38
- r = e.dxf.radius
39
- start = e.dxf.start_angle
40
- end = e.dxf.end_angle
41
- # matplotlib Arc는 (deg) 기준
42
- arc = Arc((c.x, c.y), width=2*r, height=2*r, angle=0,
43
- theta1=start, theta2=end, linewidth=0.6, color="black")
44
- ax.add_patch(arc)
45
-
46
- # 4) CIRCLE
47
- for e in msp.query("CIRCLE"):
48
- c = e.dxf.center
49
- r = e.dxf.radius
50
- circle = plt.Circle((c.x, c.y), r, fill=False, linewidth=0.6, color="black")
51
- ax.add_patch(circle)
52
-
53
- # 5) ELLIPSE (파라메트릭 샘플링)
54
- for e in msp.query("ELLIPSE"):
55
- # 중심, 주축 벡터, 종축비, 시작/끝 파라미터(t in [0, 2π))
56
- center = np.array([e.dxf.center.x, e.dxf.center.y])
57
- major = np.array([e.dxf.major_axis.x, e.dxf.major_axis.y])
58
- ratio = e.dxf.ratio # minor_len / major_len
59
- t0 = e.dxf.start_param
60
- t1 = e.dxf.end_param
61
-
62
- # 주축/종축 벡터
63
- u = major
64
- v = np.array([-major[1], major[0]]) # 직교 벡터
65
- v = v / (np.linalg.norm(v) + 1e-12) * (np.linalg.norm(major) * ratio)
66
-
67
- ts = np.linspace(t0, t1, 200)
68
- xs = center[0] + u[0]*np.cos(ts) + v[0]*np.sin(ts)
69
- ys = center[1] + u[1]*np.cos(ts) + v[1]*np.sin(ts)
70
- ax.plot(xs, ys, linewidth=0.6, color="black")
71
-
72
- # 6) SPLINE (근사)
73
- for e in msp.query("SPLINE"):
74
- pts = e.approximate(segments=200) # 복잡하면 세그먼트 증가
75
- xs, ys = zip(*[(p[0], p[1]) for p in pts])
76
- ax.plot(xs, ys, linewidth=0.6, color="black")
77
-
78
- # 7) TEXT
79
- for e in msp.query("TEXT"):
80
- ins = e.dxf.insert
81
- text = e.dxf.text
82
- height = e.dxf.height if e.dxf.height else 2.5 # 기본값
83
- rot = e.dxf.rotation if e.dxf.hasattr("rotation") else 0.0
84
- # Matplotlib 텍스트: 폰트/정렬은 완벽히 CAD와 동일하진 않음
85
- ax.text(ins.x, ins.y, text,
86
- fontsize=height, rotation=rot, rotation_mode="anchor",
87
- ha="left", va="baseline", color="black")
88
-
89
- # 8) MTEXT
90
- for e in msp.query("MTEXT"):
91
- ins = e.dxf.insert
92
- text = e.plain_text() # 포맷 태그 제거된 순수 텍스트
93
- # MTEXT는 폭/높이/줄바꿈/정렬 등 복잡: 간단히만 표시
94
- rot = e.dxf.rotation if e.dxf.hasattr("rotation") else 0.0
95
- char_height = e.dxf.char_height if e.dxf.hasattr("char_height") else 2.5
96
- ax.text(ins.x, ins.y, text,
97
- fontsize=char_height, rotation=rot, rotation_mode="anchor",
98
- ha="left", va="top", color="black")
99
-
100
- # (선택) HATCH: 경계만 외곽선으로 스케치 (간단 버전)
101
- for e in msp.query("HATCH"):
102
- for path in e.paths:
103
- if path.PATH_TYPE_EDGE: # EDGE path
104
- # EDGE는 Line/Arc/Ellipse/Spline 세그먼트, 여기선 샘플링하여 폴리라인화
105
- pts = []
106
- for edge in path.edges:
107
- typ = edge.EDGE_TYPE
108
- if typ == "LineEdge":
109
- pts += [(edge.start[0], edge.start[1]), (edge.end[0], edge.end[1])]
110
- elif typ == "ArcEdge":
111
- cx, cy = edge.center
112
- r = edge.radius
113
- a0 = math.radians(edge.start_angle)
114
- a1 = math.radians(edge.end_angle)
115
- ts = np.linspace(a0, a1, 50)
116
- pts += [(cx + r*np.cos(t), cy + r*np.sin(t)) for t in ts]
117
- elif typ == "EllipseEdge":
118
- (cx, cy) = edge.center
119
- major = np.array(edge.major_axis)
120
- ratio = edge.ratio
121
- t0, t1 = edge.start_param, edge.end_param
122
- u = major
123
- v = np.array([-major[1], major[0]])
124
- v = v / (np.linalg.norm(v) + 1e-12) * (np.linalg.norm(major) * ratio)
125
- ts = np.linspace(t0, t1, 100)
126
- pts += [(cx + u[0]*np.cos(t) + v[0]*np.sin(t),
127
- cy + u[1]*np.cos(t) + v[1]*np.sin(t)) for t in ts]
128
- elif typ == "SplineEdge":
129
- ap = edge.spline.approximate(segments=100)
130
- pts += [(p[0], p[1]) for p in ap]
131
- if len(pts) >= 2:
132
- xs, ys = zip(*pts)
133
- ax.plot(xs, ys, linewidth=0.4, color="black")
134
- elif path.PATH_TYPE_POLYLINE:
135
- pts = [(v[0], v[1]) for v in path.vertices]
136
- xs, ys = zip(*pts)
137
- ax.plot(xs, ys, linewidth=0.4, color="black")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
  ax.set_aspect("equal")
140
  ax.axis("off")
 
10
 
11
  fig, ax = plt.subplots(figsize=(8, 8))
12
 
13
+ # ---------------------------
14
+ # 공통 유틸
15
+ # ---------------------------
16
+ def plot_segments(points, closed=False, lw=0.6):
17
+ """단순 선분 연결(이미 호로 근사된 점열이라는 가정)."""
18
+ if len(points) < 2:
19
+ return
 
20
  xs, ys = zip(*points)
21
  ax.plot(xs, ys, linewidth=lw, color="black")
22
  if closed and (points[0] != points[-1]):
23
  ax.plot([points[-1][0], points[0][0]],
24
  [points[-1][1], points[0][1]], linewidth=lw, color="black")
25
 
26
+ def plot_lwpolyline(e):
27
+ """LWPOLYLINE의 bulge를 포함해 호를 근사하여 그립니다."""
28
+ # get_points("xyb") → (x, y, bulge) 튜플
29
+ pts = list(e.get_points("xyb"))
30
+ if not pts:
31
+ return
32
+ approx = []
33
+ for i in range(len(pts) - 1 + int(e.closed)):
34
+ x1, y1, b1 = pts[i % len(pts)]
35
+ x2, y2, _ = pts[(i+1) % len(pts)]
36
+ p1 = np.array([x1, y1])
37
+ p2 = np.array([x2, y2])
38
+ if abs(b1) < 1e-12:
39
+ # 직선 세그먼트
40
+ approx += [tuple(p1), tuple(p2)]
41
+ else:
42
+ # bulge 호 근사
43
+ # bulge = tan(delta/4), delta: 중심각
44
+ delta = 4 * math.atan(b1)
45
+ chord = p2 - p1
46
+ L = np.linalg.norm(chord)
47
+ if L < 1e-12:
48
+ continue
49
+ # 반지름
50
+ R = (L/2) / abs(math.sin(delta/2))
51
+ # chord 중점
52
+ mid = (p1 + p2) / 2
53
+ # chord 법선 단위벡터
54
+ n = np.array([-(chord[1]), chord[0]]) / (L + 1e-12)
55
+ # 중점에서 중심까지 거리
56
+ h = R * math.cos(delta/2)
57
+ # bulge의 부호로 중심 방향 결정
58
+ center = mid + np.sign(b1) * h * n
59
+ # p1, p2 각도
60
+ a1 = math.atan2(p1[1]-center[1], p1[0]-center[0])
61
+ a2 = math.atan2(p2[1]-center[1], p2[0]-center[0])
62
+ # 진행 방향: bulge 부호에 따라 시계/반시계
63
+ def angle_range(a_start, a_end, ccw=True, steps=32):
64
+ if ccw:
65
+ if a_end <= a_start:
66
+ a_end += 2*math.pi
67
+ return np.linspace(a_start, a_end, steps)
68
+ else:
69
+ if a_end >= a_start:
70
+ a_end -= 2*math.pi
71
+ return np.linspace(a_start, a_end, steps)
72
+ ccw = (b1 > 0) # bulge>0이면 반시계
73
+ angles = angle_range(a1, a2, ccw=ccw, steps=max(16, int(abs(delta)*16)))
74
+ for t in angles:
75
+ approx.append((center[0] + R*math.cos(t), center[1] + R*math.sin(t)))
76
+ # 중복점 정리
77
+ cleaned = []
78
+ for p in approx:
79
+ if not cleaned or (abs(cleaned[-1][0]-p[0])>1e-9 or abs(cleaned[-1][1]-p[1])>1e-9):
80
+ cleaned.append(p)
81
+ plot_segments(cleaned, closed=e.closed, lw=0.6)
82
+
83
+ def draw_basic_entity(ent):
84
+ """INSERT로 풀린 가상 엔티티 포함, 개별 엔티티를 현재 축에 그림."""
85
+ t = ent.dxftype()
86
+
87
+ if t == "LINE":
88
+ x = [ent.dxf.start.x, ent.dxf.end.x]
89
+ y = [ent.dxf.start.y, ent.dxf.end.y]
90
+ ax.plot(x, y, linewidth=0.6, color="black")
91
+
92
+ elif t == "LWPOLYLINE":
93
+ # bulge 지원
94
+ plot_lwpolyline(ent)
95
+
96
+ elif t == "POLYLINE":
97
+ pts = [(v.dxf.location.x, v.dxf.location.y) for v in ent.vertices]
98
+ plot_segments(pts, closed=getattr(ent, "is_closed", False), lw=0.6)
99
+
100
+ elif t == "ARC":
101
+ c = ent.dxf.center
102
+ r = ent.dxf.radius
103
+ arc = Arc((c.x, c.y), width=2*r, height=2*r, angle=0,
104
+ theta1=ent.dxf.start_angle, theta2=ent.dxf.end_angle,
105
+ linewidth=0.6, color="black")
106
+ ax.add_patch(arc)
107
+
108
+ elif t == "CIRCLE":
109
+ c = ent.dxf.center
110
+ r = ent.dxf.radius
111
+ circle = plt.Circle((c.x, c.y), r, fill=False, linewidth=0.6, color="black")
112
+ ax.add_patch(circle)
113
+
114
+ elif t == "ELLIPSE":
115
+ center = np.array([ent.dxf.center.x, ent.dxf.center.y])
116
+ major = np.array([ent.dxf.major_axis.x, ent.dxf.major_axis.y])
117
+ ratio = ent.dxf.ratio
118
+ t0 = ent.dxf.start_param
119
+ t1 = ent.dxf.end_param
120
+ u = major
121
+ v = np.array([-major[1], major[0]])
122
+ v = v / (np.linalg.norm(v) + 1e-12) * (np.linalg.norm(major) * ratio)
123
+ ts = np.linspace(t0, t1, 200)
124
+ xs = center[0] + u[0]*np.cos(ts) + v[0]*np.sin(ts)
125
+ ys = center[1] + u[1]*np.cos(ts) + v[1]*np.sin(ts)
126
+ ax.plot(xs, ys, linewidth=0.6, color="black")
127
+
128
+ elif t == "SPLINE":
129
+ pts = ent.approximate(segments=200)
130
+ xs, ys = zip(*[(p[0], p[1]) for p in pts])
131
+ ax.plot(xs, ys, linewidth=0.6, color="black")
132
+
133
+ elif t == "TEXT":
134
+ ins = ent.dxf.insert
135
+ text = ent.dxf.text
136
+ height = ent.dxf.height if ent.dxf.height else 2.5
137
+ rot = ent.dxf.rotation if ent.dxf.hasattr("rotation") else 0.0
138
+ ax.text(ins.x, ins.y, text, fontsize=height, rotation=rot,
139
+ rotation_mode="anchor", ha="left", va="baseline", color="black")
140
+
141
+ elif t in ("MTEXT", "ATTRIB"):
142
+ ins = ent.dxf.insert
143
+ text = ent.plain_text() if t == "MTEXT" else ent.dxf.text
144
+ rot = ent.dxf.rotation if ent.dxf.hasattr("rotation") else 0.0
145
+ char_height = getattr(ent.dxf, "char_height", None) or getattr(ent.dxf, "height", None) or 2.5
146
+ ax.text(ins.x, ins.y, text, fontsize=char_height, rotation=rot,
147
+ rotation_mode="anchor", ha="left", va="top", color="black")
148
+
149
+ elif t == "HATCH":
150
+ for path in ent.paths:
151
+ if path.PATH_TYPE_EDGE:
152
+ pts = []
153
+ for edge in path.edges:
154
+ typ = edge.EDGE_TYPE
155
+ if typ == "LineEdge":
156
+ pts += [(edge.start[0], edge.start[1]), (edge.end[0], edge.end[1])]
157
+ elif typ == "ArcEdge":
158
+ cx, cy = edge.center
159
+ r = edge.radius
160
+ a0 = math.radians(edge.start_angle)
161
+ a1 = math.radians(edge.end_angle)
162
+ ts = np.linspace(a0, a1, 50)
163
+ pts += [(cx + r*np.cos(t), cy + r*np.sin(t)) for t in ts]
164
+ elif typ == "EllipseEdge":
165
+ (cx, cy) = edge.center
166
+ major = np.array(edge.major_axis)
167
+ ratio = edge.ratio
168
+ t0, t1 = edge.start_param, edge.end_param
169
+ u = major
170
+ v = np.array([-major[1], major[0]])
171
+ v = v / (np.linalg.norm(v) + 1e-12) * (np.linalg.norm(major) * ratio)
172
+ ts = np.linspace(t0, t1, 100)
173
+ pts += [(cx + u[0]*np.cos(t) + v[0]*np.sin(t),
174
+ cy + u[1]*np.cos(t) + v[1]*np.sin(t)) for t in ts]
175
+ elif typ == "SplineEdge":
176
+ ap = edge.spline.approximate(segments=100)
177
+ pts += [(p[0], p[1]) for p in ap]
178
+ if len(pts) >= 2:
179
+ xs, ys = zip(*pts)
180
+ ax.plot(xs, ys, linewidth=0.4, color="black")
181
+ elif path.PATH_TYPE_POLYLINE:
182
+ pts = [(v[0], v[1]) for v in path.vertices]
183
+ plot_segments(pts, lw=0.4)
184
+
185
+ # ---------------------------
186
+ # 1) 모델공간 기본 엔티티
187
+ # ---------------------------
188
+ for e in msp:
189
+ # 블록 참조(INSERT)는 아래에서 따로 처리
190
+ if e.dxftype() == "INSERT":
191
+ continue
192
+ draw_basic_entity(e)
193
+
194
+ # ---------------------------
195
+ # 2) 블록(INSERT) 전개
196
+ # ---------------------------
197
+ for br in msp.query("INSERT"):
198
+ # MINSERT 포함해 배열/스케일/회전/이동이 적용된 가상 엔티티로 확장
199
+ try:
200
+ for ve in br.virtual_entities():
201
+ draw_basic_entity(ve)
202
+ except Exception:
203
+ # 누락된 블록 정의 등 오류는 넘어감
204
+ continue
205
+
206
+ # (선택) DIMENSION은 virtual_entities()를 쓰려면 보통 render() 필요
207
+ for dim in msp.query("DIMENSION"):
208
+ try:
209
+ dim.render() # 치수선→선/텍스트 등으로 실체화 (레거시 DIM에만 필요)
210
+ for ve in dim.virtual_entities():
211
+ draw_basic_entity(ve)
212
+ except Exception:
213
+ continue
214
 
215
  ax.set_aspect("equal")
216
  ax.axis("off")