From 70fa0be86f29439fcfcabbd0290638ca5baf0436 Mon Sep 17 00:00:00 2001 From: ypshim Date: Tue, 27 Oct 2026 16:47:06 +0900 Subject: [PATCH] =?UTF-8?q?fix(rebar):=20=EB=B6=80=EC=9E=AC=20fck=20?= =?UTF-8?q?=EB=90=98=EB=8F=8C=EB=A6=AC=EA=B8=B0=EA=B0=80=20=EA=B5=90?= =?UTF-8?q?=EA=B0=81=EB=8F=84=20=EA=B8=B0=EB=B3=B8=EA=B0=92=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=8F=8C=EC=95=84=EA=B0=80=EA=B3=A0,=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=EA=B3=BC=20=EB=8B=A4=EB=A5=B4=EB=A9=B4=20=EC=A3=BC?= =?UTF-8?q?=ED=99=A9=EC=83=89=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - IMPORTED_FCK(기둥 40, 코핑 40, 확대기초 27)와 fckChanged 추가, 테스트 4건 - 되돌리기는 공통 fck가 아니라 교각도 기본값으로 돌아감 (기둥·코핑은 기존에 27로 돌아가던 문제) - 기본과 다르면 입력 카드를 주황색으로 표시. "변경 n건"에는 넣지 않음 (배근에만 반영) Co-Authored-By: Claude Haiku 5.5 --- src/core/rebar/common.ts | 9 +++++++++ src/main.ts | 17 +++++++++-------- tests/member-fck.test.ts | 23 +++++++++++++++++++++++ 3 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 tests/member-fck.test.ts diff --git a/src/core/rebar/common.ts b/src/core/rebar/common.ts index 20bfa42..8ad0e78 100644 --- a/src/core/rebar/common.ts +++ b/src/core/rebar/common.ts @@ -91,6 +91,15 @@ export const FCK_MEMBERS: readonly FckMember[] = ['column', 'coping', 'footing'] /** Per-member fck (MPa). A member without an entry uses the ③ 공통 설정 fck. */ export type FckByMember = Partial>; +/** 교각도 기준 부재별 기본 fck (MPa). 되돌리기는 이 값으로 돌아가고, 이 값과 다르면 주황색으로 표시합니다. */ +export const IMPORTED_FCK: Record = { column: 40, coping: 40, footing: 27 }; + +/** True when the member's fck is set and differs from its imported default. An unset member is not changed. */ +export function fckChanged(byMember: FckByMember, member: FckMember): boolean { + const value = byMember[member]; + return value !== undefined && value !== IMPORTED_FCK[member]; +} + /** The material a member is designed with: its own fck when set, otherwise the 공통 value. fy stays common. */ export function materialFor(common: Material, byMember: FckByMember, member: FckMember): Material { return { ...common, fck: byMember[member] ?? common.fck }; diff --git a/src/main.ts b/src/main.ts index c6370da..32179f3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -14,7 +14,7 @@ import { pileGeometry } from './core/geometry'; import { rebarSummary, type RebarRow } from './core/rebar/summary'; import { shearSegments } from './core/rebar/coping'; import { buildRebar3D, type RebarColorMode, type RebarScene } from './view/rebar3d'; -import { DEFAULT_MATERIAL, FCK_MEMBERS, fyOf, GRADE_CHOICES, isAssumedMaterial, materialFrom, type BendFactors, type FckByMember, type FckMember, type GradeKey, type Material } from './core/rebar/common'; +import { DEFAULT_MATERIAL, FCK_MEMBERS, fckChanged, fyOf, GRADE_CHOICES, IMPORTED_FCK, isAssumedMaterial, materialFrom, type BendFactors, type FckByMember, type FckMember, type GradeKey, type Material } from './core/rebar/common'; import { buildQuantity, quantityRows, type Quantity, type QuantityLine } from './core/rebar/quantity'; import { parseDrawingRows } from './core/drawing-import'; import { groundElFor, parseGroundModel, strataRowsFor, type GroundModelFile } from './core/ground-model'; @@ -125,7 +125,7 @@ const state = { /** ③ 공통 설정 (fck · fy). */ material: { ...DEFAULT_MATERIAL } as Material, /** ② 부재별 fck. A member without an entry uses the 공통 fck (③). Initial values from 교각도 (025·026, 승인 2026-10-27): 기둥·코핑 40, 기초 27. */ - fckByMember: { column: 40, coping: 40, footing: 27 } as FckByMember, + fckByMember: { ...IMPORTED_FCK } as FckByMember, /** Bar highlighted on ③ after [보기] (시안 ③ 알림). */ /** Bars selected on ③ (철근표 checkboxes, 3D click, [보기]). */ rebarSelection: new Set(), @@ -1897,7 +1897,8 @@ function renderMaterial(): void { function setMemberFck(member: FckMember, raw: string): void { const next = { ...state.fckByMember }; if (raw.trim() === '') { - delete next[member]; + // 되돌리기: back to the imported default, not to the 공통 value. + next[member] = IMPORTED_FCK[member]; } else { const value = Number(raw); if (!Number.isFinite(value) || value <= 0) { @@ -1915,8 +1916,9 @@ function setMemberFck(member: FckMember, raw: string): void { /** 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]; - // Not the orange "changed" style: fck changes only the rebar, so it is not a pending shape change (변경 n건). - const row = el('div', undefined, 'edit-row'); + // Orange = differs from the imported default. fck changes only the rebar, so it is not counted in 변경 n건. + const changed = fckChanged(state.fckByMember, member); + const row = el('div', undefined, changed ? 'edit-row changed' : 'edit-row'); const id = `member-fck-${member}`; const label = el('label', 'fck (MPa)'); label.htmlFor = id; @@ -1926,9 +1928,8 @@ function renderMemberFck(box: HTMLDivElement, member: FckMember): void { input.step = 'any'; input.value = String(own ?? state.material.fck); input.addEventListener('change', () => setMemberFck(member, input.value)); - const note = own === undefined ? `공통 ${state.material.fck}` : `공통 ${state.material.fck} · 배근에만 반영`; - row.append(label, input, el('span', note, 'muted small')); - if (own !== undefined) { + row.append(label, input, el('span', `기본 ${IMPORTED_FCK[member]} · 배근에만 반영`, 'muted small')); + if (changed) { const revert = el('button', '되돌리기', 'btn small'); revert.type = 'button'; revert.addEventListener('click', () => setMemberFck(member, '')); diff --git a/tests/member-fck.test.ts b/tests/member-fck.test.ts new file mode 100644 index 0000000..c71cbf0 --- /dev/null +++ b/tests/member-fck.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from 'vitest'; +import { fckChanged, IMPORTED_FCK } from '../src/core/rebar/common'; + +describe('IMPORTED_FCK (교각도 기준 부재별 기본 fck)', () => { + it('is 40 for 기둥·코핑 and 27 for 확대기초', () => { + expect(IMPORTED_FCK).toEqual({ column: 40, coping: 40, footing: 27 }); + }); +}); + +describe('fckChanged (기본과 다르면 주황색)', () => { + it('is false at the imported default', () => { + expect(fckChanged({ column: 40, coping: 40, footing: 27 }, 'coping')).toBe(false); + }); + + it('is true when the member differs from its imported default', () => { + expect(fckChanged({ coping: 42 }, 'coping')).toBe(true); + expect(fckChanged({ footing: 30 }, 'footing')).toBe(true); + }); + + it('is false when the member has no value yet', () => { + expect(fckChanged({}, 'column')).toBe(false); + }); +});