feat(rebar): per-member fck on ② for column, coping and footing
Each member can take its own fck on its ② card. A member without one uses the ③ 공통 fck, and fy stays common. The rebar table and the 3D model both read the resolved value. The I02 note stays until every fck member has its own value. The project file stores the values as optional fckByMember and refuses a bad value on open. Changing the fck is not a shape change, so it is not counted in the pending changes. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d8fd6adcf0
commit
7cf5835689
6 files changed
+175
-18
No files matched your search
+51
-3
@@ -13,7 +13,7 @@ import { pileGeometry } from './core/geometry';
|
||||
import { rebarSummary, type RebarRow } from './core/rebar/summary';
|
||||
import { shearSegments } from './core/rebar/coping';
|
||||
import { buildRebar3D, type RebarScene } from './view/rebar3d';
|
||||
import { DEFAULT_MATERIAL, isAssumedMaterial, type Material } from './core/rebar/common';
|
||||
import { DEFAULT_MATERIAL, FCK_MEMBERS, isAssumedMaterial, type FckByMember, type FckMember, type Material } from './core/rebar/common';
|
||||
import { allVisibleChecked, setVisibleChecked, type TypeFilter } from './core/selection';
|
||||
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';
|
||||
@@ -76,6 +76,8 @@ const state = {
|
||||
stage: 'shape' as 'shape' | 'plan' | 'rebar',
|
||||
/** ③ 공통 설정 (fck · fy). */
|
||||
material: { ...DEFAULT_MATERIAL } as Material,
|
||||
/** ② 부재별 fck. A member without an entry uses the 공통 fck (③). */
|
||||
fckByMember: {} as FckByMember,
|
||||
/** Bar highlighted on ③ after [보기] (시안 ③ 알림). */
|
||||
rebarFocus: null as string | null,
|
||||
/** Member tab on ③ (철근표). */
|
||||
@@ -225,7 +227,7 @@ function setupScene(): void {
|
||||
rebar3d = null;
|
||||
const pier = activePier();
|
||||
if (pier?.model) {
|
||||
rebar3d = buildRebar3D(pier.model, state.material);
|
||||
rebar3d = buildRebar3D(pier.model, state.material, state.fckByMember);
|
||||
rebar3d.group.visible = state.stage === 'rebar';
|
||||
world.add(rebar3d.group);
|
||||
const scene = buildPierScene(pier.model);
|
||||
@@ -793,7 +795,7 @@ function renderRebar(): void {
|
||||
$('rebar-note').textContent = '교각을 생성하면 철근표가 나옵니다.';
|
||||
return;
|
||||
}
|
||||
const { rows, warnings } = rebarSummary(pier.model, state.material);
|
||||
const { rows, warnings } = rebarSummary(pier.model, state.material, state.fckByMember);
|
||||
$('rebar-note').textContent = `${pier.input.name} · 형상 기준 계산 · 값은 기본값 기준 문서 규칙`;
|
||||
const shown = rows.filter((r) => r.member === state.rebarMember);
|
||||
const table = el('table', undefined, 'grid-table rebar-table');
|
||||
@@ -918,6 +920,48 @@ function renderMaterial(): void {
|
||||
$('mat-note').textContent = assumed ? '가정값 (I02): 입력을 바꾸면 정착·이음 길이가 달라집니다.' : '입력값을 사용합니다.';
|
||||
}
|
||||
|
||||
/** Sets or clears one member's fck. An empty value clears it, so the member uses the 공통 fck again. */
|
||||
function setMemberFck(member: FckMember, raw: string): void {
|
||||
const next = { ...state.fckByMember };
|
||||
if (raw.trim() === '') {
|
||||
delete next[member];
|
||||
} else {
|
||||
const value = Number(raw);
|
||||
if (!Number.isFinite(value) || value <= 0) {
|
||||
setStatus('부재별 fck는 0보다 큰 숫자여야 합니다.');
|
||||
renderPlan();
|
||||
return;
|
||||
}
|
||||
next[member] = value;
|
||||
}
|
||||
state.fckByMember = next;
|
||||
setupScene();
|
||||
renderAll();
|
||||
}
|
||||
|
||||
/** fck row on a ② card for a member that has its own fck: value, the 공통 value beside it, and a revert when set. */
|
||||
function renderMemberFck(box: HTMLDivElement, member: FckMember): void {
|
||||
const own = state.fckByMember[member];
|
||||
const row = el('div', undefined, own === undefined ? 'edit-row' : 'edit-row changed');
|
||||
const id = `member-fck-${member}`;
|
||||
const label = el('label', 'fck (MPa)');
|
||||
label.htmlFor = id;
|
||||
const input = el('input');
|
||||
input.id = id;
|
||||
input.type = 'number';
|
||||
input.step = 'any';
|
||||
input.value = String(own ?? state.material.fck);
|
||||
input.addEventListener('change', () => setMemberFck(member, input.value));
|
||||
row.append(label, input, el('span', `공통 ${state.material.fck}`, 'muted small'));
|
||||
if (own !== undefined) {
|
||||
const revert = el('button', '되돌리기', 'btn small');
|
||||
revert.type = 'button';
|
||||
revert.addEventListener('click', () => setMemberFck(member, ''));
|
||||
row.appendChild(revert);
|
||||
}
|
||||
box.appendChild(row);
|
||||
}
|
||||
|
||||
function setMaterial(key: keyof Material, raw: string): void {
|
||||
const value = Number(raw);
|
||||
if (!Number.isFinite(value) || value <= 0) {
|
||||
@@ -965,6 +1009,7 @@ function renderPlan(): void {
|
||||
} else {
|
||||
fieldRows(fields, pier, member.keys);
|
||||
if (member.id === 'footing') renderPileRows(fields, pier);
|
||||
if (FCK_MEMBERS.includes(member.id as FckMember)) renderMemberFck(fields, member.id as FckMember);
|
||||
regen.textContent = `다시 생성 · 변경 ${pendingKeys(pier).length}`;
|
||||
regen.disabled = pendingKeys(pier).length === 0;
|
||||
}
|
||||
@@ -996,6 +1041,7 @@ function snapshot(): string {
|
||||
source: state.source,
|
||||
piers: state.piers.map((p) => ({ input: p.input, defaults: p.defaults, draft: p.draft })),
|
||||
material: state.material,
|
||||
fckByMember: state.fckByMember,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1011,6 +1057,7 @@ function currentProjectText(): string {
|
||||
piers: state.piers.map((p) => ({ id: p.input.name, input: p.input, defaults: p.defaults, draft: p.draft })),
|
||||
history: state.history,
|
||||
common: state.material,
|
||||
fckByMember: state.fckByMember,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1095,6 +1142,7 @@ function openProjectText(text: string, saved: boolean): void {
|
||||
state.source = parsed.source;
|
||||
state.history = parsed.history;
|
||||
state.material = parsed.common ? { ...parsed.common } : { ...DEFAULT_MATERIAL };
|
||||
state.fckByMember = { ...parsed.fckByMember };
|
||||
state.piers = parsed.piers.map((p) => ({
|
||||
...buildEntry(p.input, false),
|
||||
defaults: p.defaults ?? p.input,
|
||||
|
||||
Reference in new issue
Block a user