Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1

This commit is contained in:
2026-10-22 21:10:40 +09:00
6 changed files with 142 additions and 13 deletions
@@ -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)
@@ -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)
@@ -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<never, never>;
export type PolyLineJsonData = { points?: { x: number; y: number }[] };
@@ -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)
@@ -0,0 +1,49 @@
/**
* 514-3 — 가벼운 폴리선(자식 대신 꼭짓점 목록 · B07 유역도 배경선)을 CAD 가 선분으로 펼치는지.
* 실행: cd B07_DesignDetail/openwebcad && npx vitest run --config <include 를 이 파일로 바꾼 vitest.tmp.config.ts>
*/
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<PolyLineJsonData> = {
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<PolyLineJsonData>);
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]);
});
});
@@ -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