diff --git a/B07_DesignDetail/B07_DesignDetail_UI_Cad_Structures.ts b/B07_DesignDetail/B07_DesignDetail_UI_Cad_Structures.ts index 89a249f0..fa0cef2d 100644 --- a/B07_DesignDetail/B07_DesignDetail_UI_Cad_Structures.ts +++ b/B07_DesignDetail/B07_DesignDetail_UI_Cad_Structures.ts @@ -323,20 +323,55 @@ function circlePoints(center: XY, radius: number): XY[] { return points; } +/** + * `rect` → 닫힌 네 모서리 점열(회전 transform 적용). + * + * 돌쌓기 돌·돌망태 칸(`B06_Section_UI_Cross_Wall_Hatch.cell()`)은 `rect` 하나에 + * `rotate(벽기울기 cx cy)` 를 걸어 그린다. 수확기가 `rect` 를 안 보던 때는 형태 해칭 + * 80개 중 **49개(돌·칸 전부)가 CAD 에 안 실렸다**(2026-09-03 실측). 모서리 반경(`rx`)은 + * 무시한다 — 도면에서는 각진 칸으로 충분하다. + */ +function rectPoints(element: SVGElement): XY[] { + const x = attr(element, "x"); + const y = attr(element, "y"); + const width = attr(element, "width"); + const height = attr(element, "height"); + const corners: XY[] = [ + [x, y], + [x + width, y], + [x + width, y + height], + [x, y + height], + [x, y], + ]; + const rotate = /rotate\(\s*(-?[\d.]+)[\s,]+(-?[\d.]+)[\s,]+(-?[\d.]+)\s*\)/.exec( + element.getAttribute("transform") ?? "", + ); + if (!rotate) return corners.map(([cx, cy]) => flip(cx, cy)); + const angle = (Number(rotate[1]) * Math.PI) / 180; + const [ox, oy] = [Number(rotate[2]), Number(rotate[3])]; + const cos = Math.cos(angle); + const sin = Math.sin(angle); + return corners.map(([cx, cy]) => { + const dx = cx - ox; + const dy = cy - oy; + return flip(ox + dx * cos - dy * sin, oy + dx * sin + dy * cos); + }); +} + /** 오프스크린 SVG에 그려진 도형을 CAD 엔티티로 옮긴다 (블록 테두리 안으로 자른다). */ function harvest(root: SVGElement, style: Style, frame: number[]): Entity[] { const [fx0, , fx1] = frame; const entities: Entity[] = []; const segments: XY[][] = []; const inside = (px: number): boolean => px >= fx0 && px <= fx1; - const nodes = root.querySelectorAll("polygon, polyline, line, circle, text"); + const nodes = root.querySelectorAll("polygon, polyline, line, circle, rect, text"); for (const element of Array.from(nodes)) { if (element.closest(SKIP_SELECTOR)) continue; const tag = element.tagName.toLowerCase(); // 클립 그룹 안의 해칭은 경계로 **잘라서** 싣는다 — CAD 에는 클립이 없다. const clip = clipPolygonOf(element); - if (tag === "polygon" || tag === "polyline") { - const points = parsePoints(element); + if (tag === "polygon" || tag === "polyline" || tag === "rect") { + const points = tag === "rect" ? rectPoints(element) : parsePoints(element); if (tag === "polygon" && points.length > 2) points.push(points[0]); const runs = clip ? clipPointsToPolygon(points, clip) : [points]; for (const run of runs) {