Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user