실무 탭이 「위 치수조서 + 아래 수량산출서」인데 1단계에서 아래 표가 섰고 이번이 위 그림.
그림이 붙으면서 한 면에 넉 장을 쌓을 수 없게 돼 **장 나눔(㉢)도 같이** 했음.
장 나눔 — 계획평면도가 쓰는 본 그대로
`standard_sheet` / `standard_sheet_2` … 제원 조합 하나가 한 장.
좌측 목록은 `idPrefix` 묶음(계획평면도와 같은 짜임)이고 kind 는 새로 `standard`.
구조물이 없어도 한 장은 남김 — 단추가 사라지면 「없어진 것」처럼 보이고 사유를 읽을
자리도 없어짐.
그림 — `B07_DesignDetail_Engine_Cad_StandardFigure.py`
⚠ **기울기를 값으로 받음. 0.3 을 그림에 박지 않았음.** `face_slope_ratio` 가 오면 그
값으로 기울고, 비어 있으면 교본 7-3 기본 0.3 으로 그리되 **「자동 판정 전」이라고 그림에
적음**. 자동 판정이 붙는 대로 그림이 따라감.
⚠ 치수는 수량이 쓰는 그 상수(`STONE_MASONRY`)를 직접 읽음 — 여기서 다시 적으면 그림과
표가 갈림. 상부 0.45+0.10H · 하부 0.45+0.40H · 터파기 폭 평균두께+0.2 · 되메우기 0.2.
ⓘ 그 상수는 **뒷면이 수직인 1:0.3 벽**을 뜻함(하부−상부 = 0.30H = 전면이 기우는 양).
기울기가 0.3 이 아니면 그 차이만큼 뒷면이 기욺 — 상수에서 따라 나오는 결과라 그대로 그림.
⚠ 치수가 없는 것은 그리되 치수를 안 적음 — 막자갈은 「입적 − 몸통 − 고임돌」이라 폭·높이로
정의된 도형이 아님. 자리만 보이고 값은 표를 가리킴.
죽은 길 걷어냄 — `BLANK_DRAWINGS` 가 「표준도」 하나뿐이었는데 그것이 내용 있는 장이 되어
빈 도각 경로가 통째로 안 쓰이게 됨. 상수·목록 루프·분기·import 를 지움(700줄 제한도 걸렸음).
자체검증 — 회귀 573 통과 · 0 실패, `tsc --noEmit` 0.
⚠ 병합 뒤 깨진 시험 둘을 고침(내 폴더 `tmp/tests`, 랩탑 메인 `11394d40` 의 여파)
· `stone_supply='채집'` 기본값을 확정 ② 근거와 함께 허용 목록에 등록
· 바닥막이 「모든 옵션이 b05」 단정 → 조달이 detail 로 들어와 B05 셋만 보게 좁힘
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
204 lines
7.7 KiB
Python
204 lines
7.7 KiB
Python
"""표준도 **위쪽 그림** — 돌쌓기 단면(2단계, 2026-09-09).
|
|
|
|
실무 원본 탭이 「위 치수조서 + 아래 수량산출서」인데, 아래 표는 1단계에서 섰고 여기가 위다.
|
|
|
|
⚠ **기울기를 값으로 받는다 — 0.3 을 박지 않는다.**
|
|
품셈 표준경사 표 자동 판정이 다른 창에서 붙는 중이고, 붙으면 이 그림이 **그 값으로**
|
|
기울어져야 한다. `face_slope_ratio` 가 비면(=자동 미구현·미입력) 교본 7-3 기본 0.3 으로
|
|
그리되 **그 사실을 그림에 적는다**.
|
|
|
|
⚠ **수량이 쓰는 그 상수로 그린다** — `B08_Quantity_Engine_UnitQuantity.STONE_MASONRY` 를
|
|
직접 읽는다. 치수를 여기서 다시 적으면 **그림과 표가 갈린다**(CLAUDE.md 5장).
|
|
|
|
상부 두께 = 0.45 + 0.10·H 하부 두께 = 0.45 + 0.40·H
|
|
터파기 폭 = 평균두께 + 0.2 되메우기 두께 = 0.2
|
|
|
|
ⓘ 이 상수는 **뒷면이 수직인 1:0.3 벽**을 뜻한다 — 하부−상부 = 0.30·H 이고 전면이 뒤로
|
|
기우는 양도 0.3·H 라 딱 맞는다. 기울기가 0.3 이 아니면 그 차이만큼 **뒷면이 기운다**.
|
|
지어낸 것이 아니라 상수에서 따라 나오는 결과라 그대로 그린다.
|
|
|
|
⚠ **치수가 없는 것은 그리되 치수를 적지 않는다** — 막자갈(뒷채움)은 우리 식이 「입적에서
|
|
몸통·고임돌을 뺀 것」이라 폭·높이로 정의된 도형이 아니다. 자리만 보이고 값은 표를 가리킨다.
|
|
없는 치수를 그림에 적으면 **표와 다른 두 번째 정본**이 생긴다.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from B07_DesignDetail.B07_DesignDetail_Engine_Cad import (
|
|
_line_entity,
|
|
_text_entity,
|
|
polyline_entity,
|
|
)
|
|
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import STONE_MASONRY
|
|
|
|
#: 그림 축척 — 1/25(1m = 40㎜). 표(줄 높이 9㎜) 위에 얹어도 한 면에 드는 크기다.
|
|
SCALE_MM_PER_M = 40.0
|
|
|
|
#: 이 그림을 그리는 종류. 옹벽·집수정처럼 단면이 다른 것은 아직 그리지 않는다.
|
|
FIGURE_TYPE_IDS: frozenset[str] = frozenset({"masonry_wet", "masonry_dry", "boulder_masonry"})
|
|
|
|
#: 기울기를 못 받았을 때 쓰는 값 — 교본 7-3 「돌 찰쌓기 3.0m 이하 1:0.3」.
|
|
DEFAULT_SLOPE_RATIO = 0.3
|
|
|
|
_LABEL_FONT = 4.0
|
|
_DIM_FONT = 3.4
|
|
|
|
|
|
def slope_ratio_of(options: dict[str, Any] | None) -> tuple[float, bool]:
|
|
"""전면 기울기와 **그것이 입력된 값인지**. 비어 있으면 `(0.3, False)`."""
|
|
raw = (options or {}).get("face_slope_ratio")
|
|
if isinstance(raw, (int, float)) and float(raw) > 0:
|
|
return float(raw), True
|
|
return DEFAULT_SLOPE_RATIO, False
|
|
|
|
|
|
def section_points(height_m: float, slope_ratio: float) -> list[tuple[float, float]]:
|
|
"""벽 단면 네 점(m 단위, 밑면 앞끝이 원점). 앞면이 뒤로 `n·H` 기운다."""
|
|
top_t = STONE_MASONRY["thickness_base_m"] + STONE_MASONRY["thickness_top_coeff"] * height_m
|
|
bottom_t = (
|
|
STONE_MASONRY["thickness_base_m"] + STONE_MASONRY["thickness_bottom_coeff"] * height_m
|
|
)
|
|
lean = slope_ratio * height_m
|
|
return [
|
|
(0.0, 0.0),
|
|
(lean, height_m),
|
|
(lean + top_t, height_m),
|
|
(bottom_t, 0.0),
|
|
(0.0, 0.0),
|
|
]
|
|
|
|
|
|
def build_figure(
|
|
drawing_id: str,
|
|
sheet: dict[str, Any],
|
|
layer_id: str,
|
|
origin: tuple[float, float],
|
|
line_color: str,
|
|
label_color: str,
|
|
guide_color: str,
|
|
) -> tuple[list[dict[str, Any]], float]:
|
|
"""장 하나의 그림. `(엔티티, 그림이 차지한 높이 ㎜)` — 못 그리면 `([], 0)`."""
|
|
if str(sheet.get("type_id") or "") not in FIGURE_TYPE_IDS:
|
|
return [], 0.0
|
|
height_m = float(sheet.get("height_m") or 0.0)
|
|
if height_m <= 0:
|
|
return [], 0.0
|
|
|
|
options = sheet.get("options") or {}
|
|
slope, slope_given = slope_ratio_of(options)
|
|
scale = SCALE_MM_PER_M
|
|
ox, oy = origin
|
|
|
|
def mm(point: tuple[float, float]) -> tuple[float, float]:
|
|
return (ox + point[0] * scale, oy + point[1] * scale)
|
|
|
|
points = section_points(height_m, slope)
|
|
top_t = STONE_MASONRY["thickness_base_m"] + STONE_MASONRY["thickness_top_coeff"] * height_m
|
|
bottom_t = (
|
|
STONE_MASONRY["thickness_base_m"] + STONE_MASONRY["thickness_bottom_coeff"] * height_m
|
|
)
|
|
average_t = (top_t + bottom_t) / 2.0
|
|
dig_width = average_t + STONE_MASONRY["excavation_extra_m"]
|
|
backfill_t = STONE_MASONRY["backfill_thickness_m"]
|
|
|
|
entities: list[dict[str, Any]] = []
|
|
wall = polyline_entity(
|
|
f"{drawing_id}:fig:wall", [mm(p) for p in points], layer_id, line_color, width=2
|
|
)
|
|
if wall is not None:
|
|
entities.append(wall)
|
|
|
|
# 터파기 — 우리 수량식(평균두께+0.2) × 높이 를 **그대로** 그린다. 점선.
|
|
dig = polyline_entity(
|
|
f"{drawing_id}:fig:dig",
|
|
[mm(p) for p in ((0.0, 0.0), (0.0, height_m), (dig_width, height_m), (dig_width, 0.0))],
|
|
layer_id,
|
|
guide_color,
|
|
dash=[4, 3],
|
|
)
|
|
if dig is not None:
|
|
entities.append(dig)
|
|
|
|
# 되메우기 두께 선 — 밑면에서 0.2m 위. 값이 어디서 오는지 눈에 보이게.
|
|
entities.append(
|
|
_line_entity(
|
|
f"{drawing_id}:fig:backfill",
|
|
mm((0.0, backfill_t)),
|
|
mm((dig_width, backfill_t)),
|
|
layer_id,
|
|
guide_color,
|
|
)
|
|
)
|
|
|
|
# 물구멍 — 벽을 가로지르는 짧은 선 하나. 개소 간격은 글자로 적는다(면적당이라 그림에 못 씀).
|
|
weep_y = height_m * 0.5
|
|
entities.append(
|
|
_line_entity(
|
|
f"{drawing_id}:fig:weep",
|
|
mm((slope * weep_y, weep_y)),
|
|
mm((slope * weep_y + top_t, weep_y)),
|
|
layer_id,
|
|
guide_color,
|
|
)
|
|
)
|
|
|
|
labels: list[tuple[str, tuple[float, float], str]] = [
|
|
(f"H = {height_m:g} m", (-0.28, height_m / 2.0), "right"),
|
|
(f"상부 {top_t:.2f} m", (slope * height_m + top_t / 2.0, height_m + 0.14), "center"),
|
|
(f"하부 {bottom_t:.2f} m", (bottom_t / 2.0, -0.30), "center"),
|
|
(
|
|
f"1 : {slope:g}" + ("" if slope_given else " (자동 판정 전 — 교본 기본값)"),
|
|
(slope * height_m / 2.0 - 0.30, height_m * 0.72),
|
|
"right",
|
|
),
|
|
(
|
|
f"터파기 폭 {dig_width:.2f} m (평균두께 {average_t:.2f} + 0.20)",
|
|
(dig_width + 0.16, height_m * 0.92),
|
|
"left",
|
|
),
|
|
(f"되메우기 {backfill_t:g} m", (dig_width + 0.16, backfill_t), "left"),
|
|
(
|
|
f"물구멍 — {STONE_MASONRY['weep_hole_area_m2']:g}㎡당 1개소",
|
|
(slope * weep_y + top_t + 0.16, weep_y),
|
|
"left",
|
|
),
|
|
("막자갈(뒷채움) — 수량은 아래 표", (dig_width + 0.16, height_m * 0.55), "left"),
|
|
]
|
|
back = options.get("back_len_cm") or options.get("stone_back_length_cm")
|
|
if back:
|
|
labels.append((f"뒷길이 ℓ₃ = {int(back)}㎝", (dig_width + 0.16, height_m * 0.72), "left"))
|
|
|
|
for index, (text, point, align) in enumerate(labels):
|
|
x, y = mm(point)
|
|
entities.append(
|
|
_text_entity(
|
|
f"{drawing_id}:fig:label:{index}",
|
|
text,
|
|
x,
|
|
y,
|
|
layer_id,
|
|
_DIM_FONT,
|
|
label_color,
|
|
align,
|
|
)
|
|
)
|
|
|
|
title_x, title_y = mm((0.0, height_m + 0.52))
|
|
entities.append(
|
|
_text_entity(
|
|
f"{drawing_id}:fig:title",
|
|
f"{sheet.get('title') or '돌쌓기'} (축척 1/{int(1000 / SCALE_MM_PER_M)})",
|
|
title_x,
|
|
title_y,
|
|
layer_id,
|
|
_LABEL_FONT,
|
|
label_color,
|
|
"left",
|
|
)
|
|
)
|
|
|
|
used_mm = (height_m + 0.95) * scale
|
|
return entities, used_mm
|