feat(ground): 지반·지층 card inputs, layer rows, water level, section drawing
- types/ground.ts: 위치 (육상·하상·해상), 지하수위 고려 and EL, 지층 행 (name and 하단 EL, from the top down). Checks G01 (layer order), G02 (water level range), G03 (layer name). - The 지반고 still feeds the soil cover check; the layer rows and water level are inputs and do not change the 3D model. - view/ground-svg.ts: the 삽도 drawn from the inputs and the generated footings and piles on one EL scale. - 일괄 적용 copies 위치 and 지하수위 to the checked piers (속성 복사 item, off by default). Changes list shows arrays and booleans as text. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0e70cfb969
commit
16ab317094
8 files changed
+400
-6
No files matched your search
+154
-4
@@ -17,6 +17,8 @@ import { DEFAULT_MATERIAL, FCK_MEMBERS, fyOf, GRADE_CHOICES, isAssumedMaterial,
|
||||
import { buildQuantity, quantityRows, type Quantity, type QuantityLine } from './core/rebar/quantity';
|
||||
import { parseDrawingRows } from './core/drawing-import';
|
||||
import { groundElFor, parseGroundModel, type GroundModelFile } from './core/ground-model';
|
||||
import { GROUND_POSITIONS, STRATA_NAMES } from './core/ground';
|
||||
import { groundSectionSvg } from './view/ground-svg';
|
||||
import { parseBearingElevationTable, parseSolplateTable, readDxfTexts } from './core/dxf-table';
|
||||
import { columnDiameterIssues } from './core/column-check';
|
||||
import { findPileSpec, PHC_PILES, STEEL_PILES } from './core/pile-spec';
|
||||
@@ -37,7 +39,7 @@ import { allVisibleChecked, setVisibleChecked, type TypeFilter } from './core/se
|
||||
import { choose, defaultSummary, isSelectable, SHAPE_GROUPS, UNDEFINED_REASON, type MemberId } from './core/shape-options';
|
||||
import { parseProject, SCHEMA_VERSION, serializeProject, type ProjectFile, type ProjectInfo } from './core/project';
|
||||
import { clearBackup, readBackup, writeBackup, type Backup } from './view/backup';
|
||||
import type { Issue, PierInput, PierType } from './core/types';
|
||||
import type { Issue, PierInput, PierType, StrataRow } from './core/types';
|
||||
import { buildPierScene, setRebarView, viewPosition, type PartName, type PierScene, type ViewKind } from './view/scene';
|
||||
|
||||
/** One pier as shown in the list. `checked` is the multi-select state (시안 ①). */
|
||||
@@ -78,7 +80,7 @@ const MEMBERS: { id: MemberId; label: string; keys: EditableKey[]; note: string
|
||||
{ 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: '확대기초 치수, 비림, 말뚝 길이와 규격을 입력합니다. 규칙 미정 항목(모따기, 경사, 복합 말뚝)은 다음 단계에서 추가합니다.' },
|
||||
{ id: 'ground', label: '지반·지층', keys: ['groundEl'], note: '지반고는 지반 모델 값을 적용하거나 직접 입력합니다. 지층 입력은 다음 단계에서 추가합니다.' },
|
||||
{ id: 'ground', label: '지반·지층', keys: ['groundEl', 'groundPosition', 'groundWaterOn', 'groundWaterEl', 'strataRows'], note: '지반고는 계산(흙덮개)에 쓰고, 지층은 행으로, 지하수위는 직접 입력합니다. 위치는 육상·하상·해상 중 고릅니다.' },
|
||||
];
|
||||
|
||||
interface Pending {
|
||||
@@ -1498,9 +1500,157 @@ function renderBearingBasis(wrap: HTMLDivElement, pier: Pier): void {
|
||||
}
|
||||
}
|
||||
|
||||
/** ② 지반 모델 (승인 2026-10-23): the pier-centre ground EL from ground_model.json. Applied only when the user presses the button. */
|
||||
/** Text of one tracked value in the 변경 list. */
|
||||
function valueText(key: EditableKey, v: unknown): string {
|
||||
if (v === undefined || v === null) return '없음';
|
||||
if (key === 'strataRows') {
|
||||
const rows = v as StrataRow[];
|
||||
return rows.length ? rows.map((r) => `${r.name} ${r.bottomEl}`).join(' / ') : '없음';
|
||||
}
|
||||
if (typeof v === 'boolean') return v ? '예' : '아니오';
|
||||
return String(v);
|
||||
}
|
||||
|
||||
/** Sets one 지반·지층 input in the draft (applied to the model by 다시 생성). */
|
||||
function editGround(name: string, key: 'groundPosition' | 'groundWaterOn' | 'groundWaterEl', value: unknown): void {
|
||||
const p = state.piers.find((q) => q.input.name === name);
|
||||
if (!p) return;
|
||||
if (key === 'groundWaterEl' && value !== undefined && !Number.isFinite(value)) {
|
||||
setError('지하수위 EL은 숫자로 입력하세요. 값을 되돌렸습니다.');
|
||||
renderAll();
|
||||
return;
|
||||
}
|
||||
p.draft = { ...p.draft, [key]: value };
|
||||
renderAll();
|
||||
}
|
||||
|
||||
function setStrataRows(name: string, rows: StrataRow[]): void {
|
||||
const p = state.piers.find((q) => q.input.name === name);
|
||||
if (!p) return;
|
||||
p.draft = { ...p.draft, strataRows: rows };
|
||||
renderAll();
|
||||
}
|
||||
|
||||
/** 일괄 적용: the source's 위치 and 지하수위 go to the checked piers, which are then regenerated. */
|
||||
function applyGroundPositionTo(source: Pier, targets: Pier[]): void {
|
||||
for (const target of targets) {
|
||||
target.draft = applyCopy(target.draft, planCopy(source.draft, target.draft, ['ground-position']));
|
||||
}
|
||||
regenerateAllPiers();
|
||||
renderAll();
|
||||
setStatus(`${targets.length}기에 위치와 지하수위를 적용했습니다.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* ② 지반·지층 card (시안 2026-10-26; 승인 2026-10-26: 지반고는 계산에 쓰고, 지층은 행으로 입력하며, 지하수위는 직접 입력).
|
||||
* The ground model import and the 지반고 apply stay at the bottom, as before.
|
||||
*/
|
||||
function renderGroundModel(box: HTMLDivElement, pier: Pier): void {
|
||||
const name = pier.input.name;
|
||||
const d = pier.draft;
|
||||
const wrap = el('div', undefined, 'ground-model');
|
||||
|
||||
wrap.appendChild(el('p', '위치', 'lbl'));
|
||||
const positions = el('div', undefined, 'opts');
|
||||
for (const v of GROUND_POSITIONS) {
|
||||
const b = el('button', v, 'btn small');
|
||||
b.type = 'button';
|
||||
b.setAttribute('aria-pressed', String((d.groundPosition ?? '육상') === v));
|
||||
b.addEventListener('click', () => editGround(name, 'groundPosition', v));
|
||||
positions.appendChild(b);
|
||||
}
|
||||
wrap.appendChild(positions);
|
||||
|
||||
wrap.appendChild(el('p', '지하수위', 'lbl'));
|
||||
const water = el('div', undefined, 'ground-water');
|
||||
const check = el('input');
|
||||
check.type = 'checkbox';
|
||||
check.checked = d.groundWaterOn === true;
|
||||
check.id = 'ground-water-on';
|
||||
check.addEventListener('change', () => editGround(name, 'groundWaterOn', check.checked));
|
||||
const checkLabel = el('label', ' 지하수위 고려');
|
||||
checkLabel.prepend(check);
|
||||
const level = el('input');
|
||||
level.type = 'number';
|
||||
level.step = 'any';
|
||||
level.id = 'ground-water-el';
|
||||
level.placeholder = '지하수위 EL (m)';
|
||||
level.value = d.groundWaterEl === undefined ? '' : String(d.groundWaterEl);
|
||||
level.disabled = !check.checked;
|
||||
level.addEventListener('change', () => editGround(name, 'groundWaterEl', level.value === '' ? undefined : Number(level.value)));
|
||||
water.append(checkLabel, level, el('span', 'm', 'muted small'));
|
||||
wrap.appendChild(water);
|
||||
|
||||
// 지층 행 (승인 2026-10-26): one row per layer, from the top down; the EL is the layer's bottom EL.
|
||||
const rows = d.strataRows ?? [];
|
||||
wrap.appendChild(el('p', `지층 행 (위에서 아래 · 하단 EL m · 지반고 ${d.groundEl.toFixed(3)} 아래)`, 'lbl'));
|
||||
const table = el('table', undefined, 'grid-table rebar-table');
|
||||
const head = el('thead');
|
||||
const hr = el('tr');
|
||||
for (const h of ['지층', '하단 EL (m)', '']) hr.appendChild(el('th', h));
|
||||
head.appendChild(hr);
|
||||
table.appendChild(head);
|
||||
const body = el('tbody');
|
||||
rows.forEach((row, i) => {
|
||||
const tr = el('tr');
|
||||
const select = el('select');
|
||||
for (const s of STRATA_NAMES) {
|
||||
const o = el('option', s);
|
||||
o.value = s;
|
||||
select.appendChild(o);
|
||||
}
|
||||
if (!(STRATA_NAMES as readonly string[]).includes(row.name)) {
|
||||
const o = el('option', row.name);
|
||||
o.value = row.name;
|
||||
select.appendChild(o);
|
||||
}
|
||||
select.value = row.name;
|
||||
select.addEventListener('change', () => setStrataRows(name, rows.map((r, j) => (j === i ? { ...r, name: select.value } : r))));
|
||||
const bottomInput = el('input');
|
||||
bottomInput.type = 'number';
|
||||
bottomInput.step = '0.001';
|
||||
bottomInput.value = String(row.bottomEl);
|
||||
bottomInput.addEventListener('change', () => setStrataRows(name, rows.map((r, j) => (j === i ? { ...r, bottomEl: Number(bottomInput.value) } : r))));
|
||||
const del = el('button', '삭제', 'btn small');
|
||||
del.type = 'button';
|
||||
del.addEventListener('click', () => setStrataRows(name, rows.filter((_, j) => j !== i)));
|
||||
tr.append(...[select, bottomInput, del].map((x) => {
|
||||
const td = el('td');
|
||||
td.appendChild(x);
|
||||
return td;
|
||||
}));
|
||||
body.appendChild(tr);
|
||||
});
|
||||
table.appendChild(body);
|
||||
wrap.appendChild(el('div', undefined, 'table-scroll')).appendChild(table);
|
||||
const add = el('button', '지층 행 추가', 'btn small');
|
||||
add.type = 'button';
|
||||
add.addEventListener('click', () => {
|
||||
const last = rows[rows.length - 1];
|
||||
const next = last ? (STRATA_NAMES as readonly string[])[Math.min((STRATA_NAMES as readonly string[]).indexOf(last.name) + 1, STRATA_NAMES.length - 1)] : STRATA_NAMES[0];
|
||||
setStrataRows(name, [...rows, { name: next, bottomEl: Number(((last?.bottomEl ?? d.groundEl) - 2).toFixed(3)) }]);
|
||||
});
|
||||
wrap.appendChild(add);
|
||||
|
||||
// 단면 (삽도): the footings and piles are the generated model; the layers and water level are the inputs.
|
||||
if (pier.model) {
|
||||
const fig = el('figure', undefined, 'ground-fig');
|
||||
fig.innerHTML = groundSectionSvg(d, pier.model);
|
||||
fig.appendChild(el('figcaption', '단면 · 기초와 말뚝은 생성본, 지층과 지하수위는 입력값', 'muted small'));
|
||||
wrap.appendChild(fig);
|
||||
}
|
||||
|
||||
// 일괄 적용: copies the 위치 and 지하수위 to the other checked piers (속성 복사 item 'ground-position').
|
||||
const others = state.piers.filter((q) => q.checked && q.input.name !== name);
|
||||
const batch = el('button', `${name} 외 일괄 대상 ${others.length}기에 적용`, 'btn small');
|
||||
batch.type = 'button';
|
||||
batch.title = '위치와 지하수위 설정을 체크한 다른 교각에 적용합니다.';
|
||||
batch.disabled = others.length === 0;
|
||||
batch.addEventListener('click', () => applyGroundPositionTo(pier, others));
|
||||
wrap.appendChild(batch);
|
||||
|
||||
// ② 지반 모델 (승인 2026-10-23): the pier-centre ground EL from ground_model.json. Applied only when the user presses the button.
|
||||
|
||||
wrap.appendChild(el('p', '지반 모델 (지반 모델 결과 · ground_model.json)', 'lbl'));
|
||||
const importInput = el('input');
|
||||
importInput.type = 'file';
|
||||
@@ -1734,7 +1884,7 @@ function renderPlan(): void {
|
||||
changes.replaceChildren();
|
||||
const pending = pier ? pendingKeys(pier) : [];
|
||||
for (const key of pier ? overrideKeys(pier) : []) {
|
||||
const li = el('li', `${FIELD_LABELS[key]}: 기본 ${pier!.defaults[key] ?? '없음'} → 현재 ${pier!.draft[key] ?? '없음'}${pending.includes(key) ? ' (미반영)' : ''}`);
|
||||
const li = el('li', `${FIELD_LABELS[key]}: 기본 ${valueText(key, pier!.defaults[key])} → 현재 ${valueText(key, pier!.draft[key])}${pending.includes(key) ? ' (미반영)' : ''}`);
|
||||
const revert = el('button', '되돌리기', 'btn small');
|
||||
revert.type = 'button';
|
||||
revert.addEventListener('click', () => revertField(pier!.input.name, key));
|
||||
|
||||
Reference in new issue
Block a user