feat(abut): 교대 3D에 받침블럭·몰탈패드와 접속슬래브 추가

- 받침마다 블럭 1200 × 1200 (교좌면 ~ z − 50)과 몰탈패드 1000 × 1000 × 50 (spec §4.2, §6.6). 평면은 교대축 방향
- 접속슬래브 450 두께, 브라켓 상면 위, 흉벽 배면에서 5 mm 떨어져 7 m 배면으로, 배면 쪽 4 % 하향 (spec §4.8). 폭은 날개벽 사이 slabWidthMm, 축 중심
- 부재 표시: 받침·받침블럭 토글이 교대 블럭에도 적용, 접속슬래브 토글 추가
- 테스트 추가 (tests/abut-blocks-slab.test.ts). 전체 634건 통과, 타입 검사·빌드 통과. 브라우저 시각 확인은 미실시

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-11-03 15:59:51 +09:00
1 parent 5c2b23211e
commit 8f75c6dc9f
3 files changed
+121 -1

No files matched your search

+28
View File
@@ -139,6 +139,8 @@ const state = {
active: null as string | null,
view: 'iso' as ViewKind,
visible: Object.fromEntries(PART_GROUPS.flatMap((g) => g.parts).map((p) => [p, true])) as Record<PartName, boolean>,
/** 접속슬래브 toggle (abutments only). */
abutSlabVisible: true,
scene: null as PierScene | null,
project: { name: '', bridge: '', client: '', author: '', crs: CRS, memo: '' } as ProjectInfo,
/** Which stage screen is shown: ① 형상 생성 or ② 형상계획 (plan.md C1). */
@@ -358,9 +360,28 @@ function buildPartToggles(): void {
label.append(input, document.createTextNode(group.label));
row.appendChild(label);
}
// 접속슬래브 exists only on the abutments (2026-11-03): it has no pier part, so it is kept apart from state.visible.
const slabLabel = el('label');
const slabInput = el('input');
slabInput.type = 'checkbox';
slabInput.checked = state.abutSlabVisible;
slabInput.addEventListener('change', () => {
state.abutSlabVisible = slabInput.checked;
setAbutPartVisible('slab', slabInput.checked);
});
slabLabel.append(slabInput, document.createTextNode('접속슬래브'));
row.appendChild(slabLabel);
box.appendChild(row);
}
/** Shows or hides one named part of every abutment (the 받침블럭 and 접속슬래브 of abut-scene.ts). */
function setAbutPartVisible(part: 'blocks' | 'slab', on: boolean): void {
for (const g of abutGroups) {
const member = g.getObjectByName(part);
if (member) member.visible = on;
}
}
function buildViewButtons(): void {
const box = $<HTMLDivElement>('views');
for (const v of VIEWS) {
@@ -385,6 +406,8 @@ function setPartVisible(part: PartName, on: boolean): void {
else for (const s of allPiers.scenes.values()) s.parts.ground.visible = on;
return;
}
// 받침·받침블럭 also covers the abutments' blocks and pads (2026-11-03).
if (part === 'blocks') setAbutPartVisible('blocks', on);
for (const s of partScenes()) s.parts[part].visible = on;
}
@@ -501,6 +524,11 @@ function setupScene(): void {
const members = buildMembers(model, { bottomEl: rec.foundationBottomEl, pileLengthMm: rec.pileLengthMm }, rec.shape);
const group = buildAbutGroup(model, members, buildWings(model, members, rec.wings), ref.pivot, rec.groundEl, groundModel?.terrainGrid);
group.visible = abutShown(state.stage, state.abutActive, rec.name);
// The 부재 표시 toggles hold for a new abutment group too (blocks: the 받침·받침블럭 toggle; slab: 접속슬래브).
const blocks = group.getObjectByName('blocks');
if (blocks) blocks.visible = state.visible.blocks;
const slab = group.getObjectByName('slab');
if (slab) slab.visible = state.abutSlabVisible;
abutGroups.push(group);
abutGroupByName.set(rec.name, group);
world.add(group);