feat(parts): 부재 표시 항목을 교각·교대 선택과 연동
- 부재 표시 토글은 화면에 그려진 대상에만 적용. 선택한 교각에는 교각 부재, 교대에는 교대 부재가 살아 있고, 나머지 항목은 비활성 (선택 대상만) - 교대 항목 추가: 벽체·흉벽, 날개벽. 받침·받침블럭, 확대기초, 말뚝, 지반면, 접속슬래브는 교각과 교대에 함께 적용 - 값은 살아 있는 대상에서 읽고, 요약 개수는 활성 토글만 셈 - 테스트 635건 통과, 타입 검사·빌드 통과. 브라우저 시각 확인은 미실시 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fbc315fdba
commit
67bebe5ec2
1 file changed
+61
-38
+61
-38
@@ -85,15 +85,19 @@ interface Pier extends PierValues {
|
||||
/** The four stages: ① 형상 생성, ② 형상계획, ③ 철근배근, ④ 재료표 (결과 화면). */
|
||||
type Stage = 'shape' | 'plan' | 'rebar' | 'quantity';
|
||||
|
||||
const PART_GROUPS: { label: string; parts: PartName[] }[] = [
|
||||
{ label: '받침·받침블럭', parts: ['blocks', 'pads', 'bearings'] },
|
||||
{ label: '솔플레이트', parts: ['solplates'] },
|
||||
{ label: '코핑', parts: ['coping'] },
|
||||
{ label: '기둥', parts: ['columns'] },
|
||||
{ label: '확대기초', parts: ['footings'] },
|
||||
{ label: '말뚝', parts: ['piles'] },
|
||||
{ label: '버림', parts: ['blinding'] },
|
||||
{ label: '지반면', parts: ['ground'] },
|
||||
/** 부재 표시 toggles (2026-11-03). `pier` parts follow the pier scenes; `abut` names follow the abutment groups (abut-scene.ts). */
|
||||
const PART_GROUPS: { label: string; pier: PartName[]; abut: string[] }[] = [
|
||||
{ label: '받침·받침블럭', pier: ['blocks', 'pads', 'bearings'], abut: ['blocks'] },
|
||||
{ label: '솔플레이트', pier: ['solplates'], abut: [] },
|
||||
{ label: '코핑', pier: ['coping'], abut: [] },
|
||||
{ label: '기둥', pier: ['columns'], abut: [] },
|
||||
{ label: '확대기초', pier: ['footings'], abut: ['footing'] },
|
||||
{ label: '말뚝', pier: ['piles'], abut: ['piles'] },
|
||||
{ label: '버림', pier: ['blinding'], abut: [] },
|
||||
{ label: '지반면', pier: ['ground'], abut: ['ground'] },
|
||||
{ label: '벽체·흉벽', pier: [], abut: ['wall', 'parapet', 'haunches'] },
|
||||
{ label: '날개벽', pier: [], abut: ['wings'] },
|
||||
{ label: '접속슬래브', pier: [], abut: ['slab'] },
|
||||
];
|
||||
|
||||
const VIEWS: { kind: ViewKind; label: string }[] = [
|
||||
@@ -138,9 +142,9 @@ const state = {
|
||||
abutDefaults: [] as AbutRecord[],
|
||||
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,
|
||||
visible: Object.fromEntries(PART_GROUPS.flatMap((g) => g.pier).map((p) => [p, true])) as Record<PartName, boolean>,
|
||||
/** Abutment part visibility by abut-scene.ts group name (2026-11-03). */
|
||||
abutVisible: Object.fromEntries(PART_GROUPS.flatMap((g) => g.abut).map((n) => [n, true])) as Record<string, boolean>,
|
||||
scene: null as PierScene | null,
|
||||
project: { name: '', bridge: '', client: '', author: '', crs: CRS, memo: '' } as ProjectInfo,
|
||||
/** Which stage screen is shown: ① 형상 생성 or ② 형상계획 (plan.md C1). */
|
||||
@@ -343,6 +347,18 @@ function activePier(): Pier | undefined {
|
||||
}
|
||||
|
||||
// --- parts and views -------------------------------------------------------------
|
||||
/** Pier drawn in this view: all piers in 전체 보기, else the 기준 pier unless an abutment is selected in ②. */
|
||||
function pierDrawn(): boolean {
|
||||
return Boolean(allPiers) || Boolean(activePier()?.model && piersShown(state.stage, state.abutActive));
|
||||
}
|
||||
|
||||
/** Any abutment group drawn in this view. */
|
||||
function abutDrawn(): boolean {
|
||||
return abutGroups.some((g) => g.visible);
|
||||
}
|
||||
|
||||
const partToggles: { group: (typeof PART_GROUPS)[number]; input: HTMLInputElement }[] = [];
|
||||
|
||||
function buildPartToggles(): void {
|
||||
const box = $<HTMLDivElement>('parts');
|
||||
const row = el('div', undefined, 'parts-row');
|
||||
@@ -350,34 +366,43 @@ function buildPartToggles(): void {
|
||||
const label = el('label');
|
||||
const input = el('input');
|
||||
input.type = 'checkbox';
|
||||
input.checked = group.parts.every((p) => state.visible[p]);
|
||||
input.addEventListener('change', () => {
|
||||
for (const p of group.parts) {
|
||||
for (const p of group.pier) {
|
||||
state.visible[p] = input.checked;
|
||||
setPartVisible(p, input.checked);
|
||||
}
|
||||
for (const n of group.abut) {
|
||||
state.abutVisible[n] = input.checked;
|
||||
setAbutNameVisible(n, input.checked);
|
||||
}
|
||||
});
|
||||
partToggles.push({ group, input });
|
||||
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);
|
||||
updatePartToggles();
|
||||
}
|
||||
|
||||
/** Shows or hides one named part of every abutment (the 받침블럭 and 접속슬래브 of abut-scene.ts). */
|
||||
function setAbutPartVisible(part: 'blocks' | 'slab', on: boolean): void {
|
||||
/** A toggle is live when an object it names is drawn; its value comes from that object (2026-11-03: 선택 대상만). */
|
||||
function updatePartToggles(): void {
|
||||
const pier = pierDrawn();
|
||||
const abut = abutDrawn();
|
||||
for (const { group, input } of partToggles) {
|
||||
const pierLive = pier && group.pier.length > 0;
|
||||
const abutLive = abut && group.abut.length > 0;
|
||||
input.disabled = !(pierLive || abutLive);
|
||||
input.checked = pierLive
|
||||
? group.pier.every((p) => state.visible[p])
|
||||
: abutLive && group.abut.every((n) => state.abutVisible[n]);
|
||||
}
|
||||
updatePartsCount();
|
||||
}
|
||||
|
||||
/** Shows or hides one named part of every abutment (the names of abut-scene.ts). */
|
||||
function setAbutNameVisible(name: string, on: boolean): void {
|
||||
for (const g of abutGroups) {
|
||||
const member = g.getObjectByName(part);
|
||||
const member = g.getObjectByName(name);
|
||||
if (member) member.visible = on;
|
||||
}
|
||||
}
|
||||
@@ -406,8 +431,6 @@ 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;
|
||||
}
|
||||
|
||||
@@ -524,11 +547,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;
|
||||
// The 부재 표시 toggles hold for a new abutment group too.
|
||||
for (const [name, on] of Object.entries(state.abutVisible)) {
|
||||
const member = group.getObjectByName(name);
|
||||
if (member) member.visible = on;
|
||||
}
|
||||
abutGroups.push(group);
|
||||
abutGroupByName.set(rec.name, group);
|
||||
world.add(group);
|
||||
@@ -1036,7 +1059,7 @@ function renderDefaults(): void {
|
||||
let lastScrolledPier: string | null = null;
|
||||
|
||||
function renderAll(): void {
|
||||
updatePartsCount();
|
||||
updatePartToggles();
|
||||
const allToggle = $<HTMLButtonElement>('all-toggle');
|
||||
allToggle.setAttribute('aria-pressed', String(state.allView));
|
||||
allToggle.title = `${shownPiers().filter((p) => p.model).length}기를 한 화면에 (① 형상 생성)`;
|
||||
@@ -3590,8 +3613,8 @@ function setMenuOpen(which: 'views' | 'origin', open: boolean): void {
|
||||
|
||||
/** Counts the bar parts shown on the 3D view in the 부재 표시 summary. */
|
||||
function updatePartsCount(): void {
|
||||
const boxes = document.querySelectorAll<HTMLInputElement>('#parts input[type="checkbox"]');
|
||||
const on = Array.from(boxes).filter((b) => b.checked).length;
|
||||
const boxes = Array.from(document.querySelectorAll<HTMLInputElement>('#parts input[type="checkbox"]:not(:disabled)'));
|
||||
const on = boxes.filter((b) => b.checked).length;
|
||||
$('parts-count').textContent = `${on}/${boxes.length} ▾`;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user