도면층 17종을 프로젝트가 이미 쓰는 용어로 맞춘다 — 원지반·계획선·구조물· 암 경계선·수량 산출표·도각·등고선·계류·노선·배수유역·유역 제원·표제· 그래프 격자·측점표·유토곡선·그래프 축·운반 구간. CAD 앱의 'Default'와 'New layer'도 기본·새 도면층으로 바꾼다. layerId는 그대로다. 방위표 템플릿(00_template_compass.json)에 원본 DXF의 표제란 글자 4개가 도면 반대편에 남아 있었다. 이 때문에 템플릿 bbox가 183x262mm로 부풀어, 긴 변을 14mm에 맞추는 compass_entities()가 방위표를 1.1x1.8mm로 줄여 놓고 있었다. 글자를 지우니 bbox 20.5x32.9mm, 방위표가 8.7x14mm로 나온다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
537 lines
19 KiB
Python
537 lines
19 KiB
Python
"""B07 토적도(유토곡선) CAD 조립 — 확정 종단의 유토곡선 산출물을 도면으로 옮긴다.
|
|
|
|
납품 양식(2026-08-30 사용자 제시 도면):
|
|
- 제목 「유 토 곡 선」, 우측 상단 척도 표기(H=1:2,000 / V=1:50,000).
|
|
- 좌측 세로축(빨강) + 5,000㎥ 눈금·라벨, 누가토량 0 기준선.
|
|
- 유토곡선(자홍) + 블록 평형선(흰색) + 띠 경계현(노랑).
|
|
- 띠마다 balloon(장비별 육각/타원/사각) — `장비 / Q= / L= / EA= / RR= / BR=`.
|
|
(납품 도면은 장비명 뒤에 숫자가 붙지만 정체 미확인이라 적지 않는다.)
|
|
- 사토·토취 balloon — `사토 번호 / Q= / M.N= / EA= / RR= / BR=`.
|
|
- 하단 2행 테이블: 누가토량(세로쓰기) / 측점(No. 표기).
|
|
|
|
계산은 하지 않는다. 값은 B06 확정 시 저장한 `longitudinal_sections.data.mass_haul`
|
|
(정의처 `common_util_mass_haul.massHaulPayload`)을 그대로 읽어 좌표만 종이 mm로
|
|
바꾼다 — 곡선 보간·토량 배분 로직을 파이썬에 복제하지 않는다.
|
|
|
|
좌표 규약: x = 누가거리(m) x MM_H, y = 누가토량(㎥) / 종이 1mm당 토량.
|
|
"""
|
|
|
|
import math
|
|
from typing import Any
|
|
|
|
from B07_DesignDetail.B07_DesignDetail_Engine_Cad import (
|
|
DRAWING_FORMAT,
|
|
FRAME_LAYER_ID,
|
|
TABLE_LABEL_COLOR,
|
|
TABLE_LINE_COLOR,
|
|
TABLE_VALUE_COLOR,
|
|
_format,
|
|
_layer,
|
|
_line_entity,
|
|
_stations,
|
|
_text_entity,
|
|
infer_station_interval,
|
|
polyline_entity,
|
|
station_no_label,
|
|
)
|
|
from B07_DesignDetail.B07_DesignDetail_Engine_Template import entities_bbox, frame_entities
|
|
from config.config_system import (
|
|
DRAWING_SCALE_MASSHAUL_H,
|
|
DRAWING_SCALE_MASSHAUL_V_M3_MM,
|
|
)
|
|
|
|
# 도면 좌표 = 종이 mm. 실거리 1 m가 종이에서 차지하는 mm (1/2,000 -> 0.5).
|
|
MM_H = 1000.0 / DRAWING_SCALE_MASSHAUL_H
|
|
# 토량 1㎥가 종이에서 차지하는 mm (1 mm = 50㎥ -> 0.02).
|
|
MM_V = 1.0 / DRAWING_SCALE_MASSHAUL_V_M3_MM
|
|
|
|
CURVE_LAYER_ID = "b08-masshaul-curve"
|
|
CURVE_COLOR = "#ff66ff"
|
|
AXIS_LAYER_ID = "b08-masshaul-axis"
|
|
AXIS_COLOR = "#ff4d4d"
|
|
BAND_LAYER_ID = "b08-masshaul-band"
|
|
BAND_COLOR = "#ffe066"
|
|
BALANCE_COLOR = "#e8edf4"
|
|
TABLE_LAYER_ID = "b08-masshaul-table"
|
|
|
|
# 세로축 눈금 간격(㎥) — 5,000㎥ = 종이 100 mm.
|
|
AXIS_TICK_M3 = 5000.0
|
|
_AXIS_TICK_LEN = 1.2
|
|
_FONT_SIZE = 2.2
|
|
_TITLE_FONT_SIZE = 7.0
|
|
_VERTICAL = (0.0, 1.0)
|
|
|
|
# balloon 치수(mm) — 납품 도면 balloon은 6줄 세로 표기다.
|
|
_BALLOON_LINE_H = 3.0
|
|
_BALLOON_PAD_X = 2.0
|
|
_BALLOON_PAD_Y = 1.5
|
|
_BALLOON_CHAR_W = 1.35 # 글자 1개가 차지하는 가로 폭 추정(mm)
|
|
_BALLOON_GAP = 10.0 # 곡선과 balloon 사이 최소 틈(mm)
|
|
_BALLOON_STAGGER = 3 # 겹침 회피용 층 수
|
|
|
|
# 장비 키 → 도면 표기(실무 수량산출 용어: 무대·도자·덤프).
|
|
EQUIPMENT_LABEL = {
|
|
"free_haul": "무대",
|
|
"dozer": "도자",
|
|
"dump_truck": "덤프",
|
|
}
|
|
|
|
# 하단 테이블 행: (키, 헤더 라벨, 행 높이 mm).
|
|
_TABLE_ROWS: tuple[tuple[str, str, float], ...] = (
|
|
("cumulative", "누가토량", 12.0),
|
|
("station", "측점", 7.0),
|
|
)
|
|
_TABLE_LABEL_WIDTH = 16.0 # 좌측 행 이름 칸 폭(mm)
|
|
_TABLE_TOP_GAP = 6.0 # 그래프 최저점과 테이블 사이 간격(mm)
|
|
|
|
|
|
def _number(value: Any, fallback: float = 0.0) -> float:
|
|
return float(value) if isinstance(value, (int, float)) else fallback
|
|
|
|
|
|
def _curve_points(mass_haul: dict[str, Any]) -> list[tuple[float, float]]:
|
|
"""유토곡선 점열 (누가거리 m, 누가토량 ㎥). 측점 사이는 직선이다(2026-08-02 확정)."""
|
|
points = mass_haul.get("points")
|
|
if not isinstance(points, list):
|
|
return []
|
|
return [
|
|
(_number(point.get("chainage_m")), _number(point.get("cumulative_volume_m3")))
|
|
for point in points
|
|
if isinstance(point, dict) and isinstance(point.get("chainage_m"), (int, float))
|
|
]
|
|
|
|
|
|
def _paper(x_m: float, volume_m3: float) -> tuple[float, float]:
|
|
return (x_m * MM_H, volume_m3 * MM_V)
|
|
|
|
|
|
def _curve_top_at(curve: list[tuple[float, float]], from_m: float, to_m: float) -> float:
|
|
"""구간 안 곡선의 최고 누가토량(㎥). 구간에 점이 없으면 양 끝 값을 쓴다."""
|
|
inside = [v for x, v in curve if from_m - 1e-6 <= x <= to_m + 1e-6]
|
|
if inside:
|
|
return max(inside)
|
|
return max((v for _x, v in curve), default=0.0)
|
|
|
|
|
|
def _axis_entities(drawing_id: str, x0: float, min_v: float, max_v: float) -> list[dict[str, Any]]:
|
|
"""좌측 세로축(빨강)·눈금·라벨 + 누가토량 0 기준선."""
|
|
entities: list[dict[str, Any]] = []
|
|
top = _paper(0.0, max_v)[1]
|
|
bottom = _paper(0.0, min_v)[1]
|
|
entities.append(
|
|
_line_entity(f"{drawing_id}:axis:v", (x0, bottom), (x0, top), AXIS_LAYER_ID, AXIS_COLOR)
|
|
)
|
|
start = int(min_v // AXIS_TICK_M3) * AXIS_TICK_M3
|
|
value = start
|
|
while value <= max_v + 1e-6:
|
|
y = _paper(0.0, value)[1]
|
|
entities.append(
|
|
_line_entity(
|
|
f"{drawing_id}:axis:tick:{value:.0f}",
|
|
(x0 - _AXIS_TICK_LEN, y),
|
|
(x0, y),
|
|
AXIS_LAYER_ID,
|
|
AXIS_COLOR,
|
|
)
|
|
)
|
|
entities.append(
|
|
_text_entity(
|
|
f"{drawing_id}:axis:label:{value:.0f}",
|
|
_format(value, 2),
|
|
x0 - _AXIS_TICK_LEN - 0.8,
|
|
y,
|
|
AXIS_LAYER_ID,
|
|
_FONT_SIZE,
|
|
AXIS_COLOR,
|
|
align="right",
|
|
)
|
|
)
|
|
value += AXIS_TICK_M3
|
|
return entities
|
|
|
|
|
|
def _balloon_entities(
|
|
drawing_id: str,
|
|
seed: str,
|
|
lines: list[str],
|
|
anchor: tuple[float, float],
|
|
center: tuple[float, float],
|
|
shape: str,
|
|
) -> list[dict[str, Any]]:
|
|
"""balloon 도형 + 지시선 + 6줄 문자. shape = hexagon | ellipse | rect."""
|
|
width = max(len(line) for line in lines) * _BALLOON_CHAR_W + 2 * _BALLOON_PAD_X
|
|
height = len(lines) * _BALLOON_LINE_H + 2 * _BALLOON_PAD_Y
|
|
cx, cy = center
|
|
half_w, half_h = width / 2.0, height / 2.0
|
|
if shape == "hexagon":
|
|
notch = min(2.5, half_w / 2.0)
|
|
outline = [
|
|
(cx - half_w, cy),
|
|
(cx - half_w + notch, cy + half_h),
|
|
(cx + half_w - notch, cy + half_h),
|
|
(cx + half_w, cy),
|
|
(cx + half_w - notch, cy - half_h),
|
|
(cx - half_w + notch, cy - half_h),
|
|
(cx - half_w, cy),
|
|
]
|
|
elif shape == "ellipse":
|
|
# 타원은 폴리선 근사(16각) — openwebcad Ellipse 없이도 같은 인상이 난다.
|
|
outline = [
|
|
(
|
|
cx + half_w * math.cos(2 * math.pi * i / 16),
|
|
cy + half_h * math.sin(2 * math.pi * i / 16),
|
|
)
|
|
for i in range(17)
|
|
]
|
|
else:
|
|
outline = [
|
|
(cx - half_w, cy - half_h),
|
|
(cx - half_w, cy + half_h),
|
|
(cx + half_w, cy + half_h),
|
|
(cx + half_w, cy - half_h),
|
|
(cx - half_w, cy - half_h),
|
|
]
|
|
|
|
entities: list[dict[str, Any]] = []
|
|
shape_entity = polyline_entity(
|
|
drawing_id, outline, BAND_LAYER_ID, BAND_COLOR, suffix=f":balloon:{seed}"
|
|
)
|
|
if shape_entity:
|
|
entities.append(shape_entity)
|
|
# 지시선: balloon 아래(또는 위) 가장자리 → 띠 현 중앙.
|
|
edge_y = cy - half_h if anchor[1] < cy else cy + half_h
|
|
entities.append(
|
|
_line_entity(
|
|
f"{drawing_id}:balloon:leader:{seed}",
|
|
(cx, edge_y),
|
|
anchor,
|
|
BAND_LAYER_ID,
|
|
BAND_COLOR,
|
|
)
|
|
)
|
|
first_y = cy + half_h - _BALLOON_PAD_Y - _BALLOON_LINE_H * 0.75
|
|
for index, line in enumerate(lines):
|
|
entities.append(
|
|
_text_entity(
|
|
f"{drawing_id}:balloon:text:{seed}:{index}",
|
|
line,
|
|
cx,
|
|
first_y - index * _BALLOON_LINE_H,
|
|
BAND_LAYER_ID,
|
|
_FONT_SIZE,
|
|
BAND_COLOR,
|
|
)
|
|
)
|
|
return entities
|
|
|
|
|
|
def _band_entities(
|
|
drawing_id: str, plan: dict[str, Any], curve: list[tuple[float, float]]
|
|
) -> list[dict[str, Any]]:
|
|
"""블록 평형선·띠 경계현·띠 balloon."""
|
|
entities: list[dict[str, Any]] = []
|
|
blocks = plan.get("blocks")
|
|
if not isinstance(blocks, list):
|
|
return entities
|
|
slot = 0
|
|
for block in blocks:
|
|
if not isinstance(block, dict):
|
|
continue
|
|
from_m = _number(block.get("from_m"))
|
|
to_m = _number(block.get("to_m"))
|
|
base_m3 = _number(block.get("base_m3"))
|
|
entities.append(
|
|
_line_entity(
|
|
f"{drawing_id}:block:{block.get('index')}",
|
|
_paper(from_m, base_m3),
|
|
_paper(to_m, base_m3),
|
|
BAND_LAYER_ID,
|
|
BALANCE_COLOR,
|
|
)
|
|
)
|
|
for band in block.get("bands") or []:
|
|
if not isinstance(band, dict):
|
|
continue
|
|
index = band.get("index")
|
|
level_base = _number(band.get("level_base_m3"), base_m3)
|
|
level_apex = _number(band.get("level_apex_m3"), level_base)
|
|
boundary_from = _number(band.get("boundary_from_m"), from_m)
|
|
boundary_to = _number(band.get("boundary_to_m"), to_m)
|
|
haul_from = _number(band.get("haul_from_m"), boundary_from)
|
|
haul_to = _number(band.get("haul_to_m"), boundary_to)
|
|
entities.append(
|
|
_line_entity(
|
|
f"{drawing_id}:band:boundary:{index}",
|
|
_paper(boundary_from, level_base),
|
|
_paper(boundary_to, level_base),
|
|
BAND_LAYER_ID,
|
|
BAND_COLOR,
|
|
)
|
|
)
|
|
mid_level = (level_base + level_apex) / 2.0
|
|
entities.append(
|
|
_line_entity(
|
|
f"{drawing_id}:band:haul:{index}",
|
|
_paper(haul_from, mid_level),
|
|
_paper(haul_to, mid_level),
|
|
BAND_LAYER_ID,
|
|
BAND_COLOR,
|
|
)
|
|
)
|
|
equipment = str(band.get("equipment") or "")
|
|
label = EQUIPMENT_LABEL.get(equipment, "운반")
|
|
# 납품 도면은 장비명 뒤에 숫자가 붙지만(도자 11 등) 그 숫자의 정체가 확인되지
|
|
# 않아 적지 않는다 (2026-08-30 사용자 확정 — 확인되면 그때 붙인다).
|
|
lines = [
|
|
label,
|
|
f"Q={_format(_number(band.get('volume_m3')))}M3",
|
|
f"L={_format(_number(band.get('haul_distance_m')))}M",
|
|
f"EA={_format(_number(band.get('ea_m3')))}M3",
|
|
f"RR={_format(_number(band.get('rr_m3')))}M3",
|
|
f"BR={_format(_number(band.get('br_m3')))}M3",
|
|
]
|
|
anchor = _paper((haul_from + haul_to) / 2.0, mid_level)
|
|
top_m3 = _curve_top_at(curve, boundary_from, boundary_to)
|
|
height = len(lines) * _BALLOON_LINE_H + 2 * _BALLOON_PAD_Y
|
|
center_y = (
|
|
_paper(0.0, top_m3)[1] + _BALLOON_GAP + height * (0.5 + slot % _BALLOON_STAGGER)
|
|
)
|
|
entities.extend(
|
|
_balloon_entities(
|
|
drawing_id,
|
|
f"band:{index}",
|
|
lines,
|
|
anchor,
|
|
(anchor[0], center_y),
|
|
{"free_haul": "hexagon", "dozer": "ellipse"}.get(equipment, "rect"),
|
|
)
|
|
)
|
|
slot += 1
|
|
return entities
|
|
|
|
|
|
def _residual_entities(
|
|
drawing_id: str,
|
|
plan: dict[str, Any],
|
|
curve: list[tuple[float, float]],
|
|
interval_m: float,
|
|
) -> list[dict[str, Any]]:
|
|
"""사토·토취 balloon — 운반거리 대신 발생 측점(M.N)을 적는다."""
|
|
entities: list[dict[str, Any]] = []
|
|
residuals = plan.get("residuals")
|
|
if not isinstance(residuals, list):
|
|
return entities
|
|
bottom_m3 = min((v for _x, v in curve), default=0.0)
|
|
for slot, residual in enumerate(residuals):
|
|
if not isinstance(residual, dict):
|
|
continue
|
|
index = residual.get("index", slot + 1)
|
|
kind = "사토" if residual.get("kind") == "spoil" else "토취"
|
|
from_m = _number(residual.get("from_m"))
|
|
to_m = _number(residual.get("to_m"), from_m)
|
|
level = _number(residual.get("level_from_m3"))
|
|
station = station_no_label(from_m, interval_m).removeprefix("No.")
|
|
lines = [
|
|
f"{kind} {index}",
|
|
f"Q={_format(_number(residual.get('volume_m3')))}M3",
|
|
f"M.N={station}",
|
|
f"EA={_format(_number(residual.get('ea_m3')))}M3",
|
|
f"RR={_format(_number(residual.get('rr_m3')))}M3",
|
|
f"BR={_format(_number(residual.get('br_m3')))}M3",
|
|
]
|
|
anchor = _paper((from_m + to_m) / 2.0, level)
|
|
height = len(lines) * _BALLOON_LINE_H + 2 * _BALLOON_PAD_Y
|
|
center_y = (
|
|
_paper(0.0, bottom_m3)[1] - _BALLOON_GAP - height * (0.5 + slot % _BALLOON_STAGGER)
|
|
)
|
|
entities.extend(
|
|
_balloon_entities(
|
|
drawing_id, f"residual:{index}", lines, anchor, (anchor[0], center_y), "rect"
|
|
)
|
|
)
|
|
return entities
|
|
|
|
|
|
def _table_entities(
|
|
drawing_id: str,
|
|
curve: list[tuple[float, float]],
|
|
stations: list[dict[str, Any]],
|
|
interval_m: float,
|
|
top_y: float,
|
|
) -> list[dict[str, Any]]:
|
|
"""하단 2행 테이블(누가토량 / 측점). 값은 세로쓰기, 측점은 No. 표기."""
|
|
entities: list[dict[str, Any]] = []
|
|
if not curve:
|
|
return entities
|
|
cumulative = {round(x, 3): v for x, v in curve}
|
|
xs = [x for x, _v in curve]
|
|
left = min(xs) * MM_H - _TABLE_LABEL_WIDTH
|
|
right = max(xs) * MM_H
|
|
|
|
y = top_y
|
|
boundaries = [y]
|
|
for _key, _label, height in _TABLE_ROWS:
|
|
y -= height
|
|
boundaries.append(y)
|
|
for index, line_y in enumerate(boundaries):
|
|
entities.append(
|
|
_line_entity(
|
|
f"{drawing_id}:table:h:{index}",
|
|
(left, line_y),
|
|
(right, line_y),
|
|
TABLE_LAYER_ID,
|
|
TABLE_LINE_COLOR,
|
|
)
|
|
)
|
|
for index, x in enumerate((left, left + _TABLE_LABEL_WIDTH, right)):
|
|
entities.append(
|
|
_line_entity(
|
|
f"{drawing_id}:table:v:{index}",
|
|
(x, boundaries[0]),
|
|
(x, boundaries[-1]),
|
|
TABLE_LAYER_ID,
|
|
TABLE_LINE_COLOR,
|
|
)
|
|
)
|
|
|
|
for row_index, (key, label, height) in enumerate(_TABLE_ROWS):
|
|
row_top = boundaries[row_index]
|
|
row_bottom = boundaries[row_index + 1]
|
|
center_y = (row_top + row_bottom) / 2.0
|
|
entities.append(
|
|
_text_entity(
|
|
f"{drawing_id}:table:name:{key}",
|
|
label,
|
|
left + _TABLE_LABEL_WIDTH / 2.0,
|
|
center_y,
|
|
TABLE_LAYER_ID,
|
|
_FONT_SIZE,
|
|
TABLE_LABEL_COLOR,
|
|
)
|
|
)
|
|
for station in stations:
|
|
chainage = _number(station.get("chainage_m"))
|
|
x = chainage * MM_H
|
|
if x < left + _TABLE_LABEL_WIDTH or x > right:
|
|
continue
|
|
if key == "station":
|
|
text = station_no_label(chainage, interval_m)
|
|
y_text = center_y
|
|
direction = _VERTICAL
|
|
else:
|
|
value = cumulative.get(round(chainage, 3))
|
|
if value is None:
|
|
continue
|
|
text = _format(value)
|
|
y_text = row_bottom + 0.8
|
|
direction = _VERTICAL
|
|
entities.append(
|
|
_text_entity(
|
|
f"{drawing_id}:table:{key}:{chainage:.2f}",
|
|
text,
|
|
x,
|
|
y_text,
|
|
TABLE_LAYER_ID,
|
|
_FONT_SIZE,
|
|
TABLE_VALUE_COLOR,
|
|
align="left" if direction == _VERTICAL else "center",
|
|
direction=direction,
|
|
)
|
|
)
|
|
return entities
|
|
|
|
|
|
def build_mass_haul_drawing(
|
|
longitudinal: dict[str, Any], mass_haul: dict[str, Any], drawing_id: str
|
|
) -> dict[str, Any]:
|
|
"""확정 종단의 유토곡선 산출물을 토적도 한 장으로 만든다."""
|
|
curve = _curve_points(mass_haul)
|
|
if len(curve) < 2:
|
|
raise FileNotFoundError("확정 종단에 유토곡선 산출물이 없습니다. B06에서 확정하세요.")
|
|
|
|
all_stations = _stations(longitudinal)
|
|
interval_m = infer_station_interval(all_stations)
|
|
volumes = [v for _x, v in curve]
|
|
min_v, max_v = min(volumes), max(volumes)
|
|
|
|
entities: list[dict[str, Any]] = []
|
|
x0 = min(x for x, _v in curve) * MM_H
|
|
entities.extend(_axis_entities(drawing_id, x0, min(min_v, 0.0), max(max_v, 0.0)))
|
|
entities.append(
|
|
_line_entity(
|
|
f"{drawing_id}:axis:zero",
|
|
(x0, 0.0),
|
|
(max(x for x, _v in curve) * MM_H, 0.0),
|
|
AXIS_LAYER_ID,
|
|
AXIS_COLOR,
|
|
)
|
|
)
|
|
curve_entity = polyline_entity(
|
|
drawing_id, [_paper(x, v) for x, v in curve], CURVE_LAYER_ID, CURVE_COLOR
|
|
)
|
|
if curve_entity:
|
|
entities.append(curve_entity)
|
|
|
|
plan = mass_haul.get("haul_plan")
|
|
if isinstance(plan, dict):
|
|
entities.extend(_band_entities(drawing_id, plan, curve))
|
|
entities.extend(_residual_entities(drawing_id, plan, curve, interval_m))
|
|
|
|
# 테이블은 그래프·balloon 어느 것보다도 아래에 둔다 — 사토·토취 balloon이 곡선 밑에
|
|
# 깔리므로 그래프 최저점만 보고 자리를 잡으면 표와 겹친다(2026-08-30 화면 실측).
|
|
graph_bottom = min(_paper(0.0, min_v)[1], 0.0)
|
|
drawn = entities_bbox(entities)
|
|
if drawn:
|
|
graph_bottom = min(graph_bottom, drawn[1])
|
|
entities.extend(
|
|
_table_entities(drawing_id, curve, all_stations, interval_m, graph_bottom - _TABLE_TOP_GAP)
|
|
)
|
|
|
|
bbox = entities_bbox(entities)
|
|
if bbox:
|
|
min_x, _min_y, max_x, max_y = bbox
|
|
entities.append(
|
|
_text_entity(
|
|
f"{drawing_id}:title",
|
|
"유 토 곡 선",
|
|
(min_x + max_x) / 2.0,
|
|
max_y + 12.0,
|
|
AXIS_LAYER_ID,
|
|
_TITLE_FONT_SIZE,
|
|
TABLE_LABEL_COLOR,
|
|
)
|
|
)
|
|
scale_text = (
|
|
f"SCALE H=1:{DRAWING_SCALE_MASSHAUL_H:,} "
|
|
f"V=1:{int(DRAWING_SCALE_MASSHAUL_V_M3_MM * 1000):,}"
|
|
)
|
|
entities.append(
|
|
_text_entity(
|
|
f"{drawing_id}:scale",
|
|
scale_text,
|
|
max_x,
|
|
max_y + 6.0,
|
|
AXIS_LAYER_ID,
|
|
_FONT_SIZE,
|
|
TABLE_LABEL_COLOR,
|
|
align="right",
|
|
)
|
|
)
|
|
entities.extend(
|
|
frame_entities(
|
|
drawing_id, entities_bbox(entities) or bbox, fit=False, fields={"도면명": "토적도"}
|
|
)
|
|
)
|
|
|
|
return {
|
|
"format": DRAWING_FORMAT,
|
|
"entities": entities,
|
|
"layers": [
|
|
_layer(CURVE_LAYER_ID, "유토곡선"),
|
|
_layer(AXIS_LAYER_ID, "그래프 축", locked=True),
|
|
_layer(BAND_LAYER_ID, "운반 구간"),
|
|
_layer(TABLE_LAYER_ID, "측점표"),
|
|
_layer(FRAME_LAYER_ID, "도각", locked=True),
|
|
],
|
|
}
|