From c316dbb1060bc7a8c4e9d39e7d9aa8905c38c47d Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 22 Oct 2026 21:10:01 +0900 Subject: [PATCH] =?UTF-8?q?=EF=BB=BFfeat(514-3=20B07=20=EC=9C=A0=EC=97=AD?= =?UTF-8?q?=EB=8F=84=20=EB=93=B1=EA=B3=A0=EC=84=A0=20=EA=B0=80=EB=B3=8D?= =?UTF-8?q?=EA=B2=8C):=20=EC=9C=A0=EC=97=AD=EB=8F=84=20=EB=93=B1=EA=B3=A0?= =?UTF-8?q?=EC=84=A0=20=C2=B7=20=EA=B3=84=EB=A5=98=20=3D=20=ED=8F=B4?= =?UTF-8?q?=EB=A6=AC=EC=84=A0=20=ED=95=98=EB=82=98=20+=20=EA=BC=AD?= =?UTF-8?q?=EC=A7=93=EC=A0=90=20=EB=AA=A9=EB=A1=9D(=EC=9E=90=EC=8B=9D=20Li?= =?UTF-8?q?ne=20=C2=B7=20=EC=84=A0=EB=B6=84=EB=A7=88=EB=8B=A4=20UUID=20?= =?UTF-8?q?=EC=97=86=EC=9D=8C)=20=E2=80=94=20=EC=9A=94=EA=B5=AC=20328-4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 엔진: Engine_Cad_Basin 배경선만 꼭짓점 목록 폴리선(id 는 옛 폴리선과 같음) · 노선 · 유역 · 표 · 도각은 옛 꼴 그대로 · B05 · B06 길 손대지 않음 - CAD 앱: PolyLineEntity.fromJson 이 자식 없고 꼭짓점 목록이면 선분으로 펼침(색 · 굵기 · 층 = 부모) · 다시 내보내면 옛 꼴 - DXF 내보내기 · B09 도면 인쇄 PDF: 꼭짓점 목록 폴리선 = 열린 선(해치만 닫음) - 검증_sub2 유역도 5 m 23.17 → 3.22 MB · 10 m 11.91 → 1.77 MB · 좌표 · id · 색 해시 전후 같음 · B05 · B06 등고선 · 배수 응답 해시 같음 - 시험: test_514_3_b07_basin_light 새로 · vitest cad/polyline-points-514-3 새로 Co-Authored-By: Claude Opus 5.5 --- .../B07_DesignDetail_Engine_Cad_Basin.py | 27 ++++++++-- .../B07_DesignDetail_Engine_Frame_Export.py | 4 +- .../openwebcad/src/entities/PolyLineEntity.ts | 22 +++++--- .../draft/B09_Draft_Print_Drawings_Pdf.py | 3 +- .../tester/cad/polyline-points-514-3.test.ts | 49 ++++++++++++++++++ .../tester/test_514_3_b07_basin_light.py | 50 +++++++++++++++++++ 6 files changed, 142 insertions(+), 13 deletions(-) create mode 100644 resources/tester/cad/polyline-points-514-3.test.ts create mode 100644 resources/tester/test_514_3_b07_basin_light.py diff --git a/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Basin.py b/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Basin.py index 96aca087e..35f3c628e 100644 --- a/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Basin.py +++ b/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Basin.py @@ -22,6 +22,7 @@ from shapely.geometry import LineString from B07_DesignDetail.B07_DesignDetail_Engine_Cad import ( _ENTITY_NS, + _POLY_NS, DRAWING_FORMAT, FRAME_LAYER_ID, TABLE_LABEL_COLOR, @@ -125,6 +126,24 @@ def _thinned(points: list[tuple[float, float]]) -> list[tuple[float, float]]: return [(round(x, 3), round(y, 3)) for x, y in points] +def _background_entity( + drawing_id: str, points: list[tuple[float, float]], layer_id: str, color: str, suffix: str +) -> dict[str, Any] | None: + """배경선 = 폴리선 하나 + 꼭짓점 목록(자식 Line · 선분마다 UUID 없음) — 유역도 응답 크기(514-3). + + CAD 앱 `PolyLineEntity.fromJson` 이 선분으로 펼침 · id 는 `polyline_entity` 와 같음.""" + if len(points) < 2: + return None + return { + "id": str(uuid5(_POLY_NS, f"{drawing_id}:{layer_id}{suffix}")), + "type": "PolyLine", + "lineColor": color, + "lineWidth": 1, + "layerId": layer_id, + "shapeData": {"points": [{"x": x, "y": y} for x, y in points]}, + } + + def _number(value: Any, fallback: float = 0.0) -> float: return float(value) if isinstance(value, (int, float)) else fallback @@ -320,22 +339,22 @@ def build_watershed_drawing( entities: list[dict[str, Any]] = [] for index, line in enumerate(contours): - contour = polyline_entity( + contour = _background_entity( drawing_id, _thinned([paper(point) for point in line]), CONTOUR_LAYER_ID, CONTOUR_COLOR, - suffix=f":contour:{index}", + f":contour:{index}", ) if contour: entities.append(contour) for index, line in enumerate(streams): - stream = polyline_entity( + stream = _background_entity( drawing_id, _thinned([paper(point) for point in line]), STREAM_LAYER_ID, STREAM_COLOR, - suffix=f":stream:{index}", + f":stream:{index}", ) if stream: entities.append(stream) diff --git a/B07_DesignDetail/B07_DesignDetail_Engine_Frame_Export.py b/B07_DesignDetail/B07_DesignDetail_Engine_Frame_Export.py index da64d98ee..757248cf8 100644 --- a/B07_DesignDetail/B07_DesignDetail_Engine_Frame_Export.py +++ b/B07_DesignDetail/B07_DesignDetail_Engine_Frame_Export.py @@ -209,8 +209,8 @@ def _add_entity( space.add_circle(center, radius, dxfattribs=attribs) else: vertices = [xy for vertex in shape.get("points") or [] if (xy := _xy(vertex))] - if len(vertices) >= 2: - space.add_lwpolyline(vertices, close=True, dxfattribs=attribs) + if len(vertices) >= 2: # 꼭짓점 목록 폴리선(유역도 배경선 · 514-3) = 열린 선 + space.add_lwpolyline(vertices, close=kind != "PolyLine", dxfattribs=attribs) for child in entity.get("children") or []: _add_entity(space, child, layers, skipped) diff --git a/B07_DesignDetail/openwebcad/src/entities/PolyLineEntity.ts b/B07_DesignDetail/openwebcad/src/entities/PolyLineEntity.ts index 34a7c0350..af9e45afa 100644 --- a/B07_DesignDetail/openwebcad/src/entities/PolyLineEntity.ts +++ b/B07_DesignDetail/openwebcad/src/entities/PolyLineEntity.ts @@ -1,5 +1,5 @@ import type * as Flatten from '@flatten-js/core'; -import { Box, type Point, type Segment } from '@flatten-js/core'; +import { Box, Point, type Segment } from '@flatten-js/core'; import { mapLimit } from 'blend-promise-utils'; import { compact, maxBy } from 'es-toolkit'; import { minBy } from 'es-toolkit/compat'; @@ -181,10 +181,20 @@ export class PolyLineEntity implements Entity { } ); - const polyLineEntity = new PolyLineEntity( - jsonEntity.layerId || getActiveLayerId(), - compact(entities) - ); + const layerId = jsonEntity.layerId || getActiveLayerId(); + // 514-3 가벼운 꼴 — 자식 대신 꼭짓점 목록(B07 유역도 배경선) · 선분으로 펼침 + const points = jsonEntity.children?.length ? [] : (jsonEntity.shapeData?.points ?? []); + for (let index = 1; index < points.length; index++) { + const start = points[index - 1]; + const end = points[index]; + const line = new LineEntity(layerId, new Point(start.x, start.y), new Point(end.x, end.y)); + line.lineColor = jsonEntity.lineColor; + line.lineWidth = jsonEntity.lineWidth; + line.lineDash = jsonEntity.lineDash; + entities.push(line); + } + + const polyLineEntity = new PolyLineEntity(layerId, compact(entities)); polyLineEntity.id = jsonEntity.id; polyLineEntity.lineColor = jsonEntity.lineColor; polyLineEntity.lineWidth = jsonEntity.lineWidth; @@ -193,4 +203,4 @@ export class PolyLineEntity implements Entity { } } -export type PolyLineJsonData = Record; +export type PolyLineJsonData = { points?: { x: number; y: number }[] }; diff --git a/B09_Estimation/draft/B09_Draft_Print_Drawings_Pdf.py b/B09_Estimation/draft/B09_Draft_Print_Drawings_Pdf.py index 117ccfb53..d2503a454 100644 --- a/B09_Estimation/draft/B09_Draft_Print_Drawings_Pdf.py +++ b/B09_Estimation/draft/B09_Draft_Print_Drawings_Pdf.py @@ -320,7 +320,8 @@ class _Painter: vertices = [xy for v in shape.get("points") or [] if (xy := _xy(v))] if len(vertices) >= 2: self.stroke(entity) - self.line([*vertices, vertices[0]]) + # 꼭짓점 목록 폴리선(B07 유역도 배경선) = 열린 선 + self.line(vertices if kind == "PolyLine" else [*vertices, vertices[0]]) for child in entity.get("children") or []: self.entity(child) diff --git a/resources/tester/cad/polyline-points-514-3.test.ts b/resources/tester/cad/polyline-points-514-3.test.ts new file mode 100644 index 000000000..326706261 --- /dev/null +++ b/resources/tester/cad/polyline-points-514-3.test.ts @@ -0,0 +1,49 @@ +/** + * 514-3 — 가벼운 폴리선(자식 대신 꼭짓점 목록 · B07 유역도 배경선)을 CAD 가 선분으로 펼치는지. + * 실행: cd B07_DesignDetail/openwebcad && npx vitest run --config + */ +import { describe, expect, it } from "vitest"; +import { EntityName, type JsonEntity } from "../../../B07_DesignDetail/openwebcad/src/entities/Entity"; +import type { LineEntity } from "../../../B07_DesignDetail/openwebcad/src/entities/LineEntity"; +import { + PolyLineEntity, + type PolyLineJsonData, +} from "../../../B07_DesignDetail/openwebcad/src/entities/PolyLineEntity"; + +const light: JsonEntity = { + id: "p1", + type: EntityName.PolyLine, + lineColor: "#6b7684", + lineWidth: 1, + layerId: "b08-basin-contour", + shapeData: { points: [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 2 }] }, +}; + +describe("514-3 가벼운 폴리선", () => { + it("꼭짓점 n 개 = 선분 n-1 개 · 색 · 층 · id 는 부모 것", async () => { + const poly = await PolyLineEntity.fromJson(light); + expect(poly.id).toBe("p1"); + expect(poly.numberOfSegments()).toBe(2); + const lines = poly.getEntities() as LineEntity[]; + expect(lines.map((line) => [line.lineColor, line.layerId])).toEqual([ + ["#6b7684", "b08-basin-contour"], + ["#6b7684", "b08-basin-contour"], + ]); + expect(lines[1].getStartPoint().y).toBe(0); + expect(lines[1].getEndPoint().y).toBe(2); + }); + + it("다시 내보내면 옛 꼴(자식 선분) — 편집 저장 길 그대로", async () => { + const json = await (await PolyLineEntity.fromJson(light)).toJson(); + expect(json?.children?.length).toBe(2); + expect(json?.shapeData).toBeNull(); + const again = await PolyLineEntity.fromJson(json as JsonEntity); + expect(again.numberOfSegments()).toBe(2); + }); + + it("꼭짓점 하나 · 빈 값 = 선분 없음", async () => { + const one = await PolyLineEntity.fromJson({ ...light, shapeData: { points: [{ x: 0, y: 0 }] } }); + const none = await PolyLineEntity.fromJson({ ...light, shapeData: null }); + expect([one.numberOfSegments(), none.numberOfSegments()]).toEqual([0, 0]); + }); +}); diff --git a/resources/tester/test_514_3_b07_basin_light.py b/resources/tester/test_514_3_b07_basin_light.py new file mode 100644 index 000000000..03e0e66e3 --- /dev/null +++ b/resources/tester/test_514_3_b07_basin_light.py @@ -0,0 +1,50 @@ +"""514-3 — B07 유역도 배경선(등고선 · 계류) = 폴리선 하나 + 꼭짓점 목록(자식 Line 없음). + +CAD 읽기(꼭짓점 → 선분)는 vitest `cad/polyline-points-514-3`. +""" + +import io + +from ezdxf import recover + +from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Basin import ( + CONTOUR_LAYER_ID, + ROUTE_LAYER_ID, + STREAM_LAYER_ID, + build_watershed_drawing, +) +from B07_DesignDetail.B07_DesignDetail_Engine_Frame_Export import drawing_to_dxf + +ROUTE = [(0.0, 0.0), (300.0, 0.0)] +CONTOUR = [(0.0, 50.0), (100.0, 80.0), (200.0, 40.0), (300.0, 90.0)] +STREAM = [(50.0, -100.0), (60.0, 100.0)] + + +def _drawing() -> dict: + return build_watershed_drawing("plan_watershed", ROUTE, [], [CONTOUR], [STREAM]) + + +def _on(drawing: dict, layer: str) -> list[dict]: + return [e for e in drawing["entities"] if e.get("layerId") == layer] + + +def test_background_is_point_list_polyline() -> None: + drawing = _drawing() + (contour,) = _on(drawing, CONTOUR_LAYER_ID) + (stream,) = _on(drawing, STREAM_LAYER_ID) + for entity in (contour, stream): + assert entity["type"] == "PolyLine" and "children" not in entity + assert all(set(p) == {"x", "y"} for p in entity["shapeData"]["points"]) + assert len(contour["shapeData"]["points"]) == 4 + assert len(stream["shapeData"]["points"]) == 2 + # 주제선(노선)은 옛 꼴 그대로 + (route,) = _on(drawing, ROUTE_LAYER_ID) + assert len(route["children"]) == 1 + + +def test_dxf_keeps_background_open() -> None: + data, _skipped = drawing_to_dxf(_drawing()) + doc, _auditor = recover.read(io.BytesIO(data)) + polylines = [p for p in doc.modelspace().query("LWPOLYLINE") if p.dxf.layer.endswith("contour")] + assert len(polylines) == 1 + assert not polylines[0].closed and len(polylines[0]) == 4