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]