From 008b0ede143ee19dfc90cec8ad282ccab6d7fea3 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 31 Aug 2026 15:21:38 +0900 Subject: [PATCH] =?UTF-8?q?fix(B07):=20=EC=9C=A0=EC=97=AD=EB=8F=84?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=85=B8=EC=84=A0=EC=9D=84=20=EC=82=B4?= =?UTF-8?q?=EB=A6=AC=EA=B3=A0=20=EB=B0=A9=EC=9C=84=ED=91=9C=C2=B7=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=ED=91=9C=20=EB=B0=B0=EC=B9=98=EC=99=80=20=EC=A0=88?= =?UTF-8?q?=EC=B7=A8=EB=A9=B4=EC=9D=84=20=EC=A0=95=EB=A6=AC=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 노선이 유역 해칭 아래에 깔려 보이지 않았다. polyline_entity에 width를 더해 노선만 굵게(3) 그리고, 그리기 순서를 해칭·정보표 다음으로 옮긴다. bbox 계산에는 그대로 포함시켜 오른쪽 칸 위치는 흔들리지 않는다. 오른쪽 칸을 방위표(맨 위) → 유역 정보표(아래로 쌓기) 순으로 바꾸고 방위표를 14mm에서 26mm로 키운다. 도면 오른쪽 위 구석에 따로 놓던 옛 배치는 걷어낸다. 등고선·세류선 절취가 경계 바깥 점을 하나씩 물고 나와 외곽이 들쭉날쭉했다. Liang-Barsky 선분 절취로 바꿔 끝점이 경계 위에 정확히 놓이게 하고, 그만큼 두었던 물림 여유(_CLIP_SLACK 14mm)를 없앤다. Co-Authored-By: Claude Opus 5 (1M context) --- .../B07_DesignDetail_Engine_Cad.py | 6 +- .../B07_DesignDetail_Engine_Cad_Basin.py | 37 ++++++++--- .../B07_DesignDetail_Router_Support.py | 63 +++++++++++++++---- 3 files changed, 81 insertions(+), 25 deletions(-) diff --git a/B07_DesignDetail/B07_DesignDetail_Engine_Cad.py b/B07_DesignDetail/B07_DesignDetail_Engine_Cad.py index 9cf0c6d3..14e47164 100644 --- a/B07_DesignDetail/B07_DesignDetail_Engine_Cad.py +++ b/B07_DesignDetail/B07_DesignDetail_Engine_Cad.py @@ -60,12 +60,13 @@ def _line_entity( end: tuple[float, float], layer_id: str, color: str, + width: int = 1, ) -> dict[str, Any]: return { "id": str(uuid5(_ENTITY_NS, seed)), "type": "Line", "lineColor": color, - "lineWidth": 1, + "lineWidth": width, "layerId": layer_id, "shapeData": { "startPoint": {"x": start[0], "y": start[1]}, @@ -81,6 +82,7 @@ def polyline_entity( color: str, suffix: str = "", dash: list[int] | None = None, + width: int = 1, ) -> dict[str, Any] | None: """점열을 openwebcad PolyLine(자식 Line 묶음)으로 직렬화한다 (점 2개 미만이면 None).""" if len(points) < 2: @@ -89,7 +91,7 @@ def polyline_entity( children = [] for index in range(len(points) - 1): child = _line_entity( - f"{seed_base}:{index}", points[index], points[index + 1], layer_id, color + f"{seed_base}:{index}", points[index], points[index + 1], layer_id, color, width ) if dash: child["lineDash"] = dash diff --git a/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Basin.py b/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Basin.py index f266bad6..090c3991 100644 --- a/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Basin.py +++ b/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Basin.py @@ -86,7 +86,13 @@ _BOX_GAP = 3.0 _BOX_MARGIN = 12.0 # 지형 콘텐츠 오른쪽 여백 _BOX_ROWS = (("유역면적", "area"), ("유역표고", "relief"), ("유하거리", "flow")) _TITLE_BAND = 22.0 # 제목·척도가 차지하는 위쪽 띠(mm) -_CLIP_SLACK = 14.0 # 절취 경계 물림 여유(mm) +_CLIP_SLACK = 0.0 # 절취가 경계에서 정확히 끊기므로 여유가 필요 없다(2026-08-31) + +# 오른쪽 칸: 방위표를 맨 위에 놓고 그 아래로 유역 정보표를 쌓는다(2026-08-31 사용자 지시). +_COMPASS_SIZE = 26.0 # 방위표 긴 변(mm) +_COMPASS_GAP = 6.0 # 방위표와 첫 정보표 사이(mm) +# 노선은 지형·유역 위에 얹혀야 보인다 — 굵기도 배경선보다 굵게 한다. +_ROUTE_WIDTH = 3 def map_area_mm() -> tuple[float, float]: @@ -307,13 +313,15 @@ def build_watershed_drawing( ) if stream: entities.append(stream) + # 노선은 지형·유역 위에 그려야 보인다 — 여기서 만들어 두고 맨 마지막에 얹는다. route = polyline_entity( - drawing_id, [paper(point) for point in route_xy], ROUTE_LAYER_ID, ROUTE_COLOR + drawing_id, + [paper(point) for point in route_xy], + ROUTE_LAYER_ID, + ROUTE_COLOR, + width=_ROUTE_WIDTH, ) - if route: - entities.append(route) - - map_bbox = entities_bbox(entities) + map_bbox = entities_bbox([*entities, route] if route else entities) badges: list[dict[str, Any]] = [] for order, basin in enumerate(basins): ring = [paper(point) for point in basin.get("ring") or []] @@ -331,12 +339,20 @@ def build_watershed_drawing( # 번호 배지는 맨 마지막에 몰아 그린다 — 뒤에 오는 유역 해칭·정보표에 가리면 안 된다. badges.extend(_number_badge_entities(drawing_id, number, _centroid(ring), color)) - # 정보표는 지형 콘텐츠 오른쪽에 위에서부터 쌓는다. + # 오른쪽 칸은 방위표가 맨 위, 그 아래로 유역 정보표를 쌓는다. if map_bbox: box_x = map_bbox[2] + _BOX_MARGIN - box_y = map_bbox[3] + column_top = map_bbox[3] else: - box_x, box_y = 0.0, 0.0 + box_x, column_top = 0.0, 0.0 + entities.extend( + compass_entities( + drawing_id, + (box_x + _BOX_WIDTH / 2.0, column_top - _COMPASS_SIZE / 2.0), + _COMPASS_SIZE, + ) + ) + box_y = column_top - _COMPASS_SIZE - _COMPASS_GAP for order, basin in enumerate(basins): props = basin.get("props") or {} number = int(props.get("index") or order + 1) @@ -352,6 +368,8 @@ def build_watershed_drawing( ) ) + if route: + entities.append(route) # 지형·유역 위 entities.extend(badges) # 그리기 순서상 가장 위 bbox = entities_bbox(entities) @@ -380,7 +398,6 @@ def build_watershed_drawing( align="right", ) ) - entities.extend(compass_entities(drawing_id, (max_bx - 8.0, max_by - 8.0), 14.0)) entities.extend( frame_entities( drawing_id, entities_bbox(entities) or bbox, fit=False, fields={"도면명": "유역도"} diff --git a/B07_DesignDetail/B07_DesignDetail_Router_Support.py b/B07_DesignDetail/B07_DesignDetail_Router_Support.py index 89c0d075..7f558ec1 100644 --- a/B07_DesignDetail/B07_DesignDetail_Router_Support.py +++ b/B07_DesignDetail/B07_DesignDetail_Router_Support.py @@ -137,28 +137,65 @@ def _geometry_lines(geometry: Any) -> list[list[tuple[float, float]]]: return [] +def _clip_segment( + start: tuple[float, float], + end: tuple[float, float], + box: tuple[float, float, float, float], +) -> tuple[tuple[float, float], tuple[float, float]] | None: + """선분에서 상자 안에 드는 부분만 돌려준다(Liang-Barsky). 겹치지 않으면 None.""" + min_x, min_y, max_x, max_y = box + dx = end[0] - start[0] + dy = end[1] - start[1] + t0, t1 = 0.0, 1.0 + for numerator, denominator in ( + (start[0] - min_x, -dx), + (max_x - start[0], dx), + (start[1] - min_y, -dy), + (max_y - start[1], dy), + ): + if denominator == 0.0: + if numerator < 0.0: + return None # 경계와 나란한데 바깥이다 + continue + t = numerator / denominator + if denominator < 0.0: + if t > t1: + return None + t0 = max(t0, t) + else: + if t < t0: + return None + t1 = min(t1, t) + return ( + (start[0] + t0 * dx, start[1] + t0 * dy), + (start[0] + t1 * dx, start[1] + t1 * dy), + ) + + def clip_line_to_box( line: list[tuple[float, float]], box: tuple[float, float, float, float] ) -> list[list[tuple[float, float]]]: - """범위 안에 든 연속 구간만 조각으로 잘라 낸다(경계 한 점씩 물려 끊긴 티를 줄인다). + """범위 안에 든 부분만 조각으로 잘라 낸다 — 끝점은 경계 위에 정확히 놓인다. 도엽 등고선 한 줄은 도엽 끝까지 이어지므로, 걸치면 통째로 남기면 도면이 A1을 넘는다. + 예전 구현은 경계 바깥 점을 하나씩 물고 나와 외곽선이 들쭉날쭉했다(2026-08-31 사용자 + 지적). 이제 교차점을 계산해 자르므로 절취면이 곧게 떨어진다. """ - min_x, min_y, max_x, max_y = box runs: list[list[tuple[float, float]]] = [] current: list[tuple[float, float]] = [] - for index, point in enumerate(line): - x, y = point - if min_x <= x <= max_x and min_y <= y <= max_y: - if not current and index > 0: - current.append(line[index - 1]) # 바깥쪽 직전 점까지 물린다 - current.append(point) - elif current: - current.append(point) - runs.append(current) + for start, end in zip(line, line[1:]): + piece = _clip_segment(start, end, box) + if piece is None: current = [] - if current: - runs.append(current) + continue + head, tail = piece + if head == tail: + continue # 경계에 점으로만 닿았다 + if current and current[-1] == head: + current.append(tail) + else: + current = [head, tail] + runs.append(current) return [run for run in runs if len(run) >= 2]