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: ① 형상 생성, ② 형상계획, ③ 철근배근, ④ 재료표 (결과 화면). */
|
/** The four stages: ① 형상 생성, ② 형상계획, ③ 철근배근, ④ 재료표 (결과 화면). */
|
||||||
type Stage = 'shape' | 'plan' | 'rebar' | 'quantity';
|
type Stage = 'shape' | 'plan' | 'rebar' | 'quantity';
|
||||||
|
|
||||||
const PART_GROUPS: { label: string; parts: PartName[] }[] = [
|
/** 부재 표시 toggles (2026-11-03). `pier` parts follow the pier scenes; `abut` names follow the abutment groups (abut-scene.ts). */
|
||||||
{ label: '받침·받침블럭', parts: ['blocks', 'pads', 'bearings'] },
|
const PART_GROUPS: { label: string; pier: PartName[]; abut: string[] }[] = [
|
||||||
{ label: '솔플레이트', parts: ['solplates'] },
|
{ label: '받침·받침블럭', pier: ['blocks', 'pads', 'bearings'], abut: ['blocks'] },
|
||||||
{ label: '코핑', parts: ['coping'] },
|
{ label: '솔플레이트', pier: ['solplates'], abut: [] },
|
||||||
{ label: '기둥', parts: ['columns'] },
|
{ label: '코핑', pier: ['coping'], abut: [] },
|
||||||
{ label: '확대기초', parts: ['footings'] },
|
{ label: '기둥', pier: ['columns'], abut: [] },
|
||||||
{ label: '말뚝', parts: ['piles'] },
|
{ label: '확대기초', pier: ['footings'], abut: ['footing'] },
|
||||||
{ label: '버림', parts: ['blinding'] },
|
{ label: '말뚝', pier: ['piles'], abut: ['piles'] },
|
||||||
{ label: '지반면', parts: ['ground'] },
|
{ 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 }[] = [
|
const VIEWS: { kind: ViewKind; label: string }[] = [
|
||||||
@@ -138,9 +142,9 @@ const state = {
|
|||||||
abutDefaults: [] as AbutRecord[],
|
abutDefaults: [] as AbutRecord[],
|
||||||
active: null as string | null,
|
active: null as string | null,
|
||||||
view: 'iso' as ViewKind,
|
view: 'iso' as ViewKind,
|
||||||
visible: Object.fromEntries(PART_GROUPS.flatMap((g) => g.parts).map((p) => [p, true])) as Record<PartName, boolean>,
|
visible: Object.fromEntries(PART_GROUPS.flatMap((g) => g.pier).map((p) => [p, true])) as Record<PartName, boolean>,
|
||||||
/** 접속슬래브 toggle (abutments only). */
|
/** Abutment part visibility by abut-scene.ts group name (2026-11-03). */
|
||||||
abutSlabVisible: true,
|
abutVisible: Object.fromEntries(PART_GROUPS.flatMap((g) => g.abut).map((n) => [n, true])) as Record<string, boolean>,
|
||||||
scene: null as PierScene | null,
|
scene: null as PierScene | null,
|
||||||
project: { name: '', bridge: '', client: '', author: '', crs: CRS, memo: '' } as ProjectInfo,
|
project: { name: '', bridge: '', client: '', author: '', crs: CRS, memo: '' } as ProjectInfo,
|
||||||
/** Which stage screen is shown: ① 형상 생성 or ② 형상계획 (plan.md C1). */
|
/** Which stage screen is shown: ① 형상 생성 or ② 형상계획 (plan.md C1). */
|
||||||
@@ -343,6 +347,18 @@ function activePier(): Pier | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- parts and views -------------------------------------------------------------
|
// --- 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 {
|
function buildPartToggles(): void {
|
||||||
const box = $<HTMLDivElement>('parts');
|
const box = $<HTMLDivElement>('parts');
|
||||||
const row = el('div', undefined, 'parts-row');
|
const row = el('div', undefined, 'parts-row');
|
||||||
@@ -350,34 +366,43 @@ function buildPartToggles(): void {
|
|||||||
const label = el('label');
|
const label = el('label');
|
||||||
const input = el('input');
|
const input = el('input');
|
||||||
input.type = 'checkbox';
|
input.type = 'checkbox';
|
||||||
input.checked = group.parts.every((p) => state.visible[p]);
|
|
||||||
input.addEventListener('change', () => {
|
input.addEventListener('change', () => {
|
||||||
for (const p of group.parts) {
|
for (const p of group.pier) {
|
||||||
state.visible[p] = input.checked;
|
state.visible[p] = input.checked;
|
||||||
setPartVisible(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));
|
label.append(input, document.createTextNode(group.label));
|
||||||
row.appendChild(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);
|
box.appendChild(row);
|
||||||
|
updatePartToggles();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Shows or hides one named part of every abutment (the 받침블럭 and 접속슬래브 of abut-scene.ts). */
|
/** A toggle is live when an object it names is drawn; its value comes from that object (2026-11-03: 선택 대상만). */
|
||||||
function setAbutPartVisible(part: 'blocks' | 'slab', on: boolean): void {
|
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) {
|
for (const g of abutGroups) {
|
||||||
const member = g.getObjectByName(part);
|
const member = g.getObjectByName(name);
|
||||||
if (member) member.visible = on;
|
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;
|
else for (const s of allPiers.scenes.values()) s.parts.ground.visible = on;
|
||||||
return;
|
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;
|
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 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);
|
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);
|
group.visible = abutShown(state.stage, state.abutActive, rec.name);
|
||||||
// The 부재 표시 toggles hold for a new abutment group too (blocks: the 받침·받침블럭 toggle; slab: 접속슬래브).
|
// The 부재 표시 toggles hold for a new abutment group too.
|
||||||
const blocks = group.getObjectByName('blocks');
|
for (const [name, on] of Object.entries(state.abutVisible)) {
|
||||||
if (blocks) blocks.visible = state.visible.blocks;
|
const member = group.getObjectByName(name);
|
||||||
const slab = group.getObjectByName('slab');
|
if (member) member.visible = on;
|
||||||
if (slab) slab.visible = state.abutSlabVisible;
|
}
|
||||||
abutGroups.push(group);
|
abutGroups.push(group);
|
||||||
abutGroupByName.set(rec.name, group);
|
abutGroupByName.set(rec.name, group);
|
||||||
world.add(group);
|
world.add(group);
|
||||||
@@ -1036,7 +1059,7 @@ function renderDefaults(): void {
|
|||||||
let lastScrolledPier: string | null = null;
|
let lastScrolledPier: string | null = null;
|
||||||
|
|
||||||
function renderAll(): void {
|
function renderAll(): void {
|
||||||
updatePartsCount();
|
updatePartToggles();
|
||||||
const allToggle = $<HTMLButtonElement>('all-toggle');
|
const allToggle = $<HTMLButtonElement>('all-toggle');
|
||||||
allToggle.setAttribute('aria-pressed', String(state.allView));
|
allToggle.setAttribute('aria-pressed', String(state.allView));
|
||||||
allToggle.title = `${shownPiers().filter((p) => p.model).length}기를 한 화면에 (① 형상 생성)`;
|
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. */
|
/** Counts the bar parts shown on the 3D view in the 부재 표시 summary. */
|
||||||
function updatePartsCount(): void {
|
function updatePartsCount(): void {
|
||||||
const boxes = document.querySelectorAll<HTMLInputElement>('#parts input[type="checkbox"]');
|
const boxes = Array.from(document.querySelectorAll<HTMLInputElement>('#parts input[type="checkbox"]:not(:disabled)'));
|
||||||
const on = Array.from(boxes).filter((b) => b.checked).length;
|
const on = boxes.filter((b) => b.checked).length;
|
||||||
$('parts-count').textContent = `${on}/${boxes.length} ▾`;
|
$('parts-count').textContent = `${on}/${boxes.length} ▾`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user