feat(abut): ③ 교대 반투명 보기 · 교대 저판 전용 fck
- ③ 철근배근에서 교대 부재·지반도 교각처럼 반투명(0.5, 깊이 쓰기 끔). setRebarViewOn으로 공통화, 끄면 원래 불투명도 복원(지반 0.35 유지) - 교대 저판 fck(abutFooting, 기본 27)를 교각 기초 fck와 분리. ② 확대기초·말뚝 카드에 입력, ③ 저판 배근·이음 길이에 반영 - 테스트 713개 통과 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b06bf368b6
commit
603066f370
6 files changed
+89
-24
No files matched your search
+1
-1
@@ -68,7 +68,7 @@ export function parseProject(text: string): ProjectFile {
|
||||
if (!Array.isArray(obj.piers)) throw new Error('piers 목록이 없습니다.');
|
||||
if (obj.fckByMember !== undefined) {
|
||||
for (const [member, value] of Object.entries(obj.fckByMember)) {
|
||||
if (!['column', 'coping', 'footing'].includes(member) || typeof value !== 'number' || !(value > 0)) {
|
||||
if (!['column', 'coping', 'footing', 'abutFooting'].includes(member) || typeof value !== 'number' || !(value > 0)) {
|
||||
throw new Error(`부재별 fck 값이 맞지 않습니다 (${member}: ${String(value)}).`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,15 +84,15 @@ export function materialFrom(raw: unknown): Material {
|
||||
}
|
||||
|
||||
/** Members that can take their own fck on ② (부재 카드). Other members always use the 공통 fck. */
|
||||
export type FckMember = 'column' | 'coping' | 'footing';
|
||||
export type FckMember = 'column' | 'coping' | 'footing' | 'abutFooting';
|
||||
/** The members of {@link FckMember}, for the ② cards. */
|
||||
export const FCK_MEMBERS: readonly FckMember[] = ['column', 'coping', 'footing'];
|
||||
export const FCK_MEMBERS: readonly FckMember[] = ['column', 'coping', 'footing', 'abutFooting'];
|
||||
|
||||
/** Per-member fck (MPa). A member without an entry uses the ③ 공통 설정 fck. */
|
||||
export type FckByMember = Partial<Record<FckMember, number>>;
|
||||
|
||||
/** 교각도 기준 부재별 기본 fck (MPa). 되돌리기는 이 값으로 돌아가고, 이 값과 다르면 주황색으로 표시합니다. */
|
||||
export const IMPORTED_FCK: Record<FckMember, number> = { column: 40, coping: 40, footing: 27 };
|
||||
/** 교각도 · 교대도 기준 부재별 기본 fck (MPa). 되돌리기는 이 값으로 돌아가고, 이 값과 다르면 주황색으로 표시합니다. 교대 저판 27 (spec_abut §6, 2026-11-03). */
|
||||
export const IMPORTED_FCK: Record<FckMember, number> = { column: 40, coping: 40, footing: 27, abutFooting: 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 {
|
||||
|
||||
+17
-3
@@ -109,7 +109,7 @@ import { choose, defaultSummary, isSelectable, SHAPE_GROUPS, UNDEFINED_REASON, t
|
||||
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, StrataRow } from './core/types';
|
||||
import { buildPierScene, setRebarView, viewPosition, type PartName, type PierScene, type ViewKind } from './view/scene';
|
||||
import { buildPierScene, setRebarView, setRebarViewOn, viewPosition, type PartName, type PierScene, type ViewKind } from './view/scene';
|
||||
import { buildAllPiers, type AllPiers } from './view/all-piers';
|
||||
import { buildTerrainMapGroup, disposeTerrainMapGroup, setTerrainMapMode } from './view/terrain-map';
|
||||
|
||||
@@ -631,6 +631,7 @@ function setupScene(): void {
|
||||
const member = group.getObjectByName(name);
|
||||
if (member) member.visible = on;
|
||||
}
|
||||
setRebarViewOn(group, state.stage === 'rebar');
|
||||
abutGroups.push(group);
|
||||
abutGroupByName.set(rec.name, group);
|
||||
world.add(group);
|
||||
@@ -1486,6 +1487,18 @@ function fillAbutFootingExtras(
|
||||
auto('길이 (자동)', `${L.toFixed(3)} m`, '교좌면 길이 (skew)');
|
||||
auto('말뚝 길이', `${Math.round(rec.pileLengthMm)} mm`, '① 기본 정보에서 수정');
|
||||
auto('선단 EL (자동)', built.members.pileBottomEl === undefined ? '—' : `${built.members.pileBottomEl.toFixed(3)} m`, '= 기초 하단 − 말뚝 길이');
|
||||
// 교대 저판 fck: 교각 기초 fck와 따로 둔다 (사용자 결정 2026-11-04). 비우면 기본값으로 돌아간다.
|
||||
const fckId = idOf('fck');
|
||||
const fckLab = el('label', 'fck (MPa)');
|
||||
fckLab.htmlFor = fckId;
|
||||
const fckIn = el('input');
|
||||
fckIn.id = fckId;
|
||||
fckIn.type = 'number';
|
||||
fckIn.step = 'any';
|
||||
fckIn.value = String(state.fckByMember.abutFooting ?? state.material.fck);
|
||||
if (fckChanged(state.fckByMember, 'abutFooting')) fckIn.classList.add('changed-input');
|
||||
fckIn.addEventListener('change', () => setMemberFck('abutFooting', fckIn.value));
|
||||
box.append(fckLab, fckIn, el('span', `기본 ${IMPORTED_FCK.abutFooting} · 교각 기초와 별도 · 배근에만 반영`, 'muted small'));
|
||||
// 열 위치 (배면부터): "650 · 2000 · 1300 · 650". The last one (전면 연단) follows from the 폭.
|
||||
const id = idOf('pile-rows');
|
||||
const lab = el('label', '열 위치 · 배면부터 (mm)');
|
||||
@@ -2549,6 +2562,7 @@ function setStage(stage: Stage): void {
|
||||
// Entering or leaving ② changes which objects are drawn (visibility.ts), so the scene is rebuilt then.
|
||||
if ((allPiers !== null) !== multiSceneOn() || stage === 'plan' || previous === 'plan') setupScene();
|
||||
if (state.scene) setRebarView(state.scene, stage === 'rebar');
|
||||
for (const g of abutGroups) setRebarViewOn(g, stage === 'rebar');
|
||||
$<HTMLElement>('view-shape').hidden = stage !== 'shape';
|
||||
$<HTMLElement>('view-plan').hidden = stage !== 'plan';
|
||||
$<HTMLElement>('view-rebar').hidden = stage !== 'rebar';
|
||||
@@ -3083,7 +3097,7 @@ function abutFootingBuilt(rec: AbutRecord): { geom: AbutFootingGeom; result: Abu
|
||||
const model = buildAbut(toAbutInput(rec));
|
||||
const members = buildMembers(model, { bottomEl: rec.foundationBottomEl, pileLengthMm: rec.pileLengthMm }, rec.shape);
|
||||
const geom = abutFootingGeom(model.length, members.shape, skewAngle(rec.skewDeg));
|
||||
const result = abutFootingRebar(geom, rec.rebar?.footing, materialFor(state.material, state.fckByMember, 'footing'), rec.name);
|
||||
const result = abutFootingRebar(geom, rec.rebar?.footing, materialFor(state.material, state.fckByMember, 'abutFooting'), rec.name);
|
||||
return { geom, result, skewK: abutSkewK(model) };
|
||||
} catch {
|
||||
return null;
|
||||
@@ -3273,7 +3287,7 @@ function fillAbutFootingRebarCard(box: HTMLDivElement, rec: AbutRecord, built: {
|
||||
grid2.append(cell);
|
||||
}
|
||||
// 자동 값.
|
||||
const fck = materialFor(state.material, state.fckByMember, 'footing').fck;
|
||||
const fck = materialFor(state.material, state.fckByMember, 'abutFooting').fck;
|
||||
const fy = (size: string) => fyOf(state.material, size);
|
||||
const auto = (label: string, value: string) => {
|
||||
const cell = el('div', undefined, 'abut-rebar-auto');
|
||||
|
||||
+15
-11
@@ -208,17 +208,21 @@ const REBAR_VIEW_ALPHA = 0.5;
|
||||
|
||||
/** Turns the ③ rebar view on or off. Off restores each part's own opacity. Depth writes stop while on, so bars behind a member stay visible. */
|
||||
export function setRebarView(scene: PierScene, on: boolean): void {
|
||||
for (const group of Object.values(scene.parts)) {
|
||||
group.traverse((o) => {
|
||||
if (!(o instanceof THREE.Mesh)) return;
|
||||
const mat = o.material as THREE.MeshStandardMaterial;
|
||||
const base = mat.userData.baseOpacity as number;
|
||||
mat.opacity = on ? REBAR_VIEW_ALPHA : base;
|
||||
mat.transparent = on || base < 1;
|
||||
mat.depthWrite = !on;
|
||||
mat.needsUpdate = true;
|
||||
});
|
||||
}
|
||||
for (const group of Object.values(scene.parts)) setRebarViewOn(group, on);
|
||||
}
|
||||
|
||||
/** {@link setRebarView} for any group (교대 3D). A material without a recorded base opacity takes its current one as the base. */
|
||||
export function setRebarViewOn(root: THREE.Object3D, on: boolean): void {
|
||||
root.traverse((o) => {
|
||||
if (!(o instanceof THREE.Mesh)) return;
|
||||
const mat = o.material as THREE.MeshStandardMaterial;
|
||||
mat.userData.baseOpacity ??= mat.opacity;
|
||||
const base = mat.userData.baseOpacity as number;
|
||||
mat.opacity = on ? REBAR_VIEW_ALPHA : base;
|
||||
mat.transparent = on || base < 1;
|
||||
mat.depthWrite = !on && base >= 1;
|
||||
mat.needsUpdate = true;
|
||||
});
|
||||
}
|
||||
|
||||
export type ViewKind = 'iso' | 'front' | 'side' | 'top';
|
||||
|
||||
Reference in new issue
Block a user