feat(view): 철근표 and the 3D rebar model point at each other (③)
- The 3D model of ③ draws each bar family from the rebar modules (coping top and bottom bars, shear loops, side bars, column bars and ties, footing B1–B8, bearing S1/S2) and names every bar as its 철근표 number. - A table row click highlights the bar in 3D (the others dim); a click on a 3D bar selects its row in the table and sets the member tab. Drags still rotate. - The rebar is shown only on ③. Not drawn yet: C14, C15/C16, B9–B12, the couplers, and the bars of a bent coping. - Tests: family names and counts (P1 46), highlight behaviour. - Not verified in a browser. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8c96ab76f5
commit
a692f1f507
3 files changed
+277
-1
No files matched your search
+47
-1
@@ -12,6 +12,7 @@ import { applyCopy, DEFAULT_COPY_ITEMS, planCopy, type CopyItem } from './core/c
|
||||
import { pileGeometry } from './core/geometry';
|
||||
import { rebarSummary, type RebarRow } from './core/rebar/summary';
|
||||
import { shearSegments } from './core/rebar/coping';
|
||||
import { buildRebar3D, type RebarScene } from './view/rebar3d';
|
||||
import { DEFAULT_MATERIAL, isAssumedMaterial, type Material } from './core/rebar/common';
|
||||
import { allVisibleChecked, setVisibleChecked, type TypeFilter } from './core/selection';
|
||||
import { choose, defaultSummary, isSelectable, SHAPE_GROUPS, UNDEFINED_REASON, type MemberId } from './core/shape-options';
|
||||
@@ -92,6 +93,8 @@ const state = {
|
||||
};
|
||||
|
||||
let pendingBackup: Backup | null = null;
|
||||
/** 3D rebar of the current pier (③); its bars are named as in the 철근표. */
|
||||
let rebar3d: RebarScene | null = null;
|
||||
let backupTimer: number | undefined;
|
||||
|
||||
const $ = <T extends HTMLElement>(id: string): T => document.getElementById(id) as T;
|
||||
@@ -218,8 +221,13 @@ function setView(kind: ViewKind): void {
|
||||
function setupScene(): void {
|
||||
if (state.scene) world.remove(state.scene.root);
|
||||
state.scene = null;
|
||||
if (rebar3d) world.remove(rebar3d.group);
|
||||
rebar3d = null;
|
||||
const pier = activePier();
|
||||
if (pier?.model) {
|
||||
rebar3d = buildRebar3D(pier.model);
|
||||
rebar3d.group.visible = state.stage === 'rebar';
|
||||
world.add(rebar3d.group);
|
||||
const scene = buildPierScene(pier.model);
|
||||
for (const key of Object.keys(state.visible) as PartName[]) {
|
||||
scene.parts[key].visible = state.visible[key];
|
||||
@@ -698,6 +706,37 @@ function placeViewer(stage: 'shape' | 'plan' | 'rebar'): void {
|
||||
resize();
|
||||
}
|
||||
|
||||
/** The 철근표 group that a bar name belongs to. */
|
||||
function memberOfBar(name: string): RebarRow['member'] {
|
||||
if (name === 'S1' || name === 'S2') return 'bearing';
|
||||
if (name.startsWith('B')) return 'footing';
|
||||
if (name.startsWith('P') || name === '커플러') return 'column';
|
||||
return 'coping';
|
||||
}
|
||||
|
||||
/** Click on a 3D bar selects its 철근표 row (on ③ only). */
|
||||
function wireRebarPicking(): void {
|
||||
const dom = renderer.domElement;
|
||||
let down: { x: number; y: number } | null = null;
|
||||
dom.addEventListener('pointerdown', (e) => {
|
||||
down = { x: e.clientX, y: e.clientY };
|
||||
});
|
||||
dom.addEventListener('pointerup', (e) => {
|
||||
if (!down || state.stage !== 'rebar' || !rebar3d) return;
|
||||
if (Math.hypot(e.clientX - down.x, e.clientY - down.y) > 5) return; // a drag rotates the view
|
||||
const rect = dom.getBoundingClientRect();
|
||||
const ndc = new THREE.Vector2(((e.clientX - rect.left) / rect.width) * 2 - 1, -((e.clientY - rect.top) / rect.height) * 2 + 1);
|
||||
const ray = new THREE.Raycaster();
|
||||
ray.setFromCamera(ndc, camera);
|
||||
const hit = ray.intersectObjects(rebar3d.group.children, false)[0];
|
||||
if (!hit) return;
|
||||
const name = hit.object.userData.name as string;
|
||||
state.rebarFocus = name;
|
||||
state.rebarMember = memberOfBar(name);
|
||||
renderAll();
|
||||
});
|
||||
}
|
||||
|
||||
function setStage(stage: 'shape' | 'plan' | 'rebar'): void {
|
||||
state.stage = stage;
|
||||
$<HTMLElement>('view-shape').hidden = stage !== 'shape';
|
||||
@@ -705,6 +744,7 @@ function setStage(stage: 'shape' | 'plan' | 'rebar'): void {
|
||||
$<HTMLElement>('view-rebar').hidden = stage !== 'rebar';
|
||||
// Move the viewer after its new parent is visible, so that resize() measures the real size.
|
||||
placeViewer(stage);
|
||||
if (rebar3d) rebar3d.group.visible = stage === 'rebar';
|
||||
// The highlight is the 'current' class (style.css); aria-current is set alongside it for assistive tech.
|
||||
const nav: [string, 'shape' | 'plan' | 'rebar'][] = [['nav-shape', 'shape'], ['nav-plan', 'plan'], ['nav-rebar', 'rebar']];
|
||||
for (const [id, key] of nav) {
|
||||
@@ -768,7 +808,10 @@ function renderRebar(): void {
|
||||
const nameCell = el('td', r.name);
|
||||
nameCell.classList.add('bar-no');
|
||||
// Clicking a 번호 highlights that row (시안: 번호를 누르면 그 철근만 강조).
|
||||
nameCell.addEventListener('click', () => tr.classList.toggle('hl'));
|
||||
nameCell.addEventListener('click', () => {
|
||||
state.rebarFocus = state.rebarFocus === r.name ? null : r.name;
|
||||
renderAll();
|
||||
});
|
||||
if (r.name === state.rebarFocus) tr.classList.add('hl');
|
||||
tr.append(
|
||||
el('td', ROLE[r.name] ?? '—'),
|
||||
@@ -844,6 +887,8 @@ function renderRebar(): void {
|
||||
box.appendChild(el('p', '전단철근 구간 (끝면 기준, 철근 위치의 처음과 끝)', 'lbl'));
|
||||
box.appendChild(st);
|
||||
}
|
||||
// Table and 3D model point at each other: the focused bar is highlighted in both.
|
||||
rebar3d?.highlight(state.rebarFocus);
|
||||
const focused = box.querySelector('tr.hl');
|
||||
if (focused && state.rebarFocus) focused.scrollIntoView({ block: 'center' });
|
||||
}
|
||||
@@ -1401,6 +1446,7 @@ $<HTMLButtonElement>('discard-backup').addEventListener('click', () => {
|
||||
|
||||
// --- start ------------------------------------------------------------------------------
|
||||
captureViewerHome();
|
||||
wireRebarPicking();
|
||||
buildPartToggles();
|
||||
buildViewButtons();
|
||||
renderDefaults();
|
||||
|
||||
Reference in new issue
Block a user