feat(bearing): 일체 choice gated by the pair eccentricity rule (승인 2026-10-26)

- PierInput.bearingLayout: 분리 (default) or 일체, tracked for changes.
- 일체 is selectable only when there is at least one pair and every pair is
  within 100 mm; otherwise the chip is disabled with the reason.
- Validation: B01 (info) when 일체 is chosen and allowed, the block shape is
  still the 분리 shape; B02 (warning) when it is not allowed and the model
  falls back to 분리.
- The 일체 block shape (2500 × 1200) is not modelled yet.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-26 11:56:26 +09:00
1 parent cd18665d6a
commit 2af5cfb144
7 files changed
+75 -7

No files matched your search

+19 -4
View File
@@ -18,7 +18,7 @@ import { buildQuantity, quantityRows, type Quantity, type QuantityLine } from '.
import { parseDrawingRows } from './core/drawing-import';
import { groundElFor, parseGroundModel, strataRowsFor, type GroundModelFile } from './core/ground-model';
import { CORROSION_ALLOWANCE_MM, parseNValue, SEA_DATUM, STRATA_NAMES } from './core/ground';
import { bearingEdgeDistances, PAIR_ECCENTRICITY_LIMIT_MM, pairEccentricities } from './core/bearing-layout';
import { bearingEdgeDistances, integratedCheck, PAIR_ECCENTRICITY_LIMIT_MM, pairEccentricities } from './core/bearing-layout';
import { groundSectionSvg } from './view/ground-svg';
import { parseBearingElevationTable, parseSolplateTable, readDxfTexts } from './core/dxf-table';
import { columnDiameterIssues } from './core/column-check';
@@ -77,7 +77,7 @@ const CRS = '국토좌표계 (x = 동, y = 북)';
/** 시안 ② 부재 목록 (spec §5.2). `keys` are the inputs that exist today; the rest show values only. */
const MEMBERS: { id: MemberId; label: string; keys: EditableKey[]; note: string }[] = [
{ id: 'bearing', label: '교량받침·받침블럭·솔플레이트', keys: ['bearingType'], note: '받침 상세(C3)는 Q16 결정 후 추가합니다. 지금은 표시만 합니다.' },
{ id: 'bearing', label: '교량받침·받침블럭·솔플레이트', keys: ['bearingType', 'bearingLayout'], note: '받침 상세(C3)는 Q16 결정 후 추가합니다. 지금은 표시만 합니다.' },
{ id: 'coping', label: '코핑', keys: ['copingWidthMm', 'copingLengthMm', 'copingH1Mm', 'copingH2Mm', 'levelWidthMm'], note: '폭, 길이, 끝단 높이 h1, 테이퍼 높이 h2, Level 폭을 입력합니다. 계단 위치·단차선 입력은 다음 단계에서 추가합니다.' },
{ id: 'column', label: '기둥', keys: ['columnDiameterMm'], note: '기둥 직경 D를 입력합니다 (1200–3000 mm, 50 mm 단위). 높이와 위치는 계산값입니다.' },
{ id: 'footing', label: '확대기초 · 말뚝', keys: ['footingLengthMm', 'footingWidthMm', 'footingHeightMm', 'blindingMarginMm', 'blindingThicknessMm', 'pileLengthMm'], note: '확대기초 치수, 비림, 말뚝 길이와 규격을 입력합니다. 규칙 미정 항목(모따기, 경사, 복합 말뚝)은 다음 단계에서 추가합니다.' },
@@ -845,14 +845,21 @@ function renderShapeOptions(member: MemberId, pier?: Pier): void {
const chip = el('button', option.label, 'chip-opt');
chip.type = 'button';
// The ground 위치 is stored in the pier's draft, so its pressed chip is the chosen one (승인 2026-10-26).
const chosen = member === 'ground' && pier ? (pier.draft.groundPosition ?? '육상') : undefined;
const chosen = member === 'ground' && pier ? (pier.draft.groundPosition ?? '육상') : member === 'bearing' && pier ? (pier.draft.bearingLayout ?? '분리') : undefined;
chip.setAttribute('aria-pressed', String(chosen !== undefined ? option.label === chosen : option.isDefault));
chip.disabled = !isSelectable(option);
if (chip.disabled) chip.title = UNDEFINED_REASON;
// 일체 needs every front–back pair within 100 mm (승인 2026-10-26); the reason is shown on the chip.
if (member === 'bearing' && option.label === '일체' && pier?.model) {
const check = integratedCheck(pier.model.blocks);
chip.disabled = !check.allowed;
chip.title = check.allowed ? '' : `편심 100 mm 초과 쌍 ${check.overLimit.length}개 → 분리 우선`;
}
chip.addEventListener('click', () => {
try {
choose(member, gi, option.label);
if (member === 'ground' && pier) editGround(pier.input.name, 'groundPosition', option.label);
if (member === 'bearing' && pier) editBearingLayout(pier.input.name, option.label as '분리' | '일체');
} catch (err) {
setError((err as Error).message);
}
@@ -1542,6 +1549,14 @@ function valueText(key: EditableKey, v: unknown): string {
return String(v);
}
/** Sets 받침 배치 (분리 / 일체) in the draft (applied by 다시 생성). */
function editBearingLayout(name: string, value: '분리' | '일체'): void {
const p = state.piers.find((q) => q.input.name === name);
if (!p) return;
p.draft = { ...p.draft, bearingLayout: value };
renderAll();
}
/** Sets one 지반·지층 input in the draft (applied to the model by 다시 생성). */
function editGround(name: string, key: 'groundPosition' | 'groundWaterOn' | 'groundWaterEl' | 'floodLevelEl' | 'scourDepthM' | 'highWaterEl' | 'lowWaterEl', value: unknown): void {
const p = state.piers.find((q) => q.input.name === name);
@@ -1956,7 +1971,7 @@ function renderPlan(): void {
$('member-title').textContent = member.label;
// Default and current selection are shown separately (spec §5.1). Only defaults are selectable today.
// 선택 shows the pier's own 위치 for the ground; the other members still choose only their defaults.
const selected = member.id === 'ground' && pier ? (pier.draft.groundPosition ?? '육상') : defaultSummary(member.id);
const selected = member.id === 'ground' && pier ? (pier.draft.groundPosition ?? '육상') : member.id === 'bearing' && pier ? (pier.draft.bearingLayout ?? '분리') : defaultSummary(member.id);
$('member-summary').textContent = `기본: ${defaultSummary(member.id)} · 선택: ${selected}`;
renderShapeOptions(member.id, pier);
$('member-note').textContent = member.note;