feat(view): colour the ③ 3D rebar by diameter or by member, with a legend

Role colours stay the default (spec §6.7). A control on ③ switches to diameter (nominal size, H and S of one size
share a colour, Okabe–Ito palette) or member (받침블럭 · 코핑 · 기둥 · 확대기초). The legend lists only the items
the model has. Highlighting and the state colours are unchanged. Approved 2026-10-23.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-23 09:10:20 +09:00
1 parent 5d5ad7e997
commit d35e27e7de
6 files changed
+176 -3

No files matched your search

+39 -2
View File
@@ -6,6 +6,7 @@ import { C14_INSET, c14Bars, CAP, COPING_REBAR, shearBars, shearBarsMulti, shear
import { columnRebar, COLUMN_P1 } from '../core/rebar/column';
import { barsAcross, BLOCK_REBAR } from '../core/rebar/bearing-block';
import { DEFAULT_MATERIAL, FckByMember, hookLength, Material, materialFor, NOMINAL_DIAMETER } from '../core/rebar/common';
import { diameterGroupIndex, DIAMETER_GROUPS, memberOfBar, type RebarMemberKey } from '../core/rebar/color';
import { stretchMatrix } from './scene';
/**
@@ -18,8 +19,21 @@ export interface RebarScene {
byName: Map<string, THREE.Mesh[]>;
/** Highlight one bar name (or none): the others are dimmed. */
highlight(name: string | null): void;
/** Colour the bars by role (default, spec §6.7), by nominal diameter, or by member. */
setColorMode(mode: RebarColorMode): void;
/** Legend entries of the mode, only for the items this model has. Role mode has none (the role colours are in §6.7). */
legend(mode: RebarColorMode): { label: string; color: string }[];
}
export type RebarColorMode = 'role' | 'diameter' | 'member';
/** Okabe–Ito colours (colour-blind safe), one per diameter group in order; grey for anything else. */
const DIAMETER_COLORS = [0xe69f00, 0x56b4e9, 0x009e73, 0x0072b2, 0xd55e00, 0xcc79a7, 0x000000, 0x8c8c8c];
const OTHER_COLOR = 0x8c8c8c;
const MEMBER_COLORS: Record<RebarMemberKey, number> = { bearing: 0xe69f00, coping: 0x0072b2, column: 0x009e73, footing: 0xcc79a7, other: OTHER_COLOR };
const MEMBER_LABELS: Record<RebarMemberKey, string> = { bearing: '받침블럭', coping: '코핑', column: '기둥', footing: '확대기초', other: '기타' };
const hex = (v: number) => `#${v.toString(16).padStart(6, '0')}`;
const COLOR = {
coping: 0xb4531f,
shear: 0x6b3fa0,
@@ -54,7 +68,7 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
geo.applyMatrix4(Ms[k]);
const mat = new THREE.MeshStandardMaterial({ color, roughness: 0.6, metalness: 0.1 });
const mesh = new THREE.Mesh(geo, mat);
mesh.userData.name = name;
mesh.userData = { name, roleColor: color, dMm, member: memberOfBar(name) };
group.add(mesh);
const list = byName.get(name) ?? [];
list.push(mesh);
@@ -436,5 +450,28 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
}
}
};
return { group, byName, highlight };
const colorOf = (mesh: THREE.Mesh, mode: RebarColorMode): number => {
const data = mesh.userData as { roleColor: number; dMm: number; member: RebarMemberKey };
if (mode === 'role') return data.roleColor;
if (mode === 'member') return MEMBER_COLORS[data.member];
const idx = diameterGroupIndex(data.dMm);
return idx < 0 ? OTHER_COLOR : DIAMETER_COLORS[idx];
};
const setColorMode = (mode: RebarColorMode) => {
for (const meshes of byName.values()) {
for (const mesh of meshes) (mesh.material as THREE.MeshStandardMaterial).color.setHex(colorOf(mesh, mode));
}
};
const legend = (mode: RebarColorMode) => {
const all = [...byName.values()].flat().map((mesh) => mesh.userData as { dMm: number; member: RebarMemberKey });
if (mode === 'role') return [];
if (mode === 'member') {
const present = new Set(all.map((d) => d.member));
return (Object.keys(MEMBER_LABELS) as RebarMemberKey[]).filter((k) => present.has(k)).map((k) => ({ label: MEMBER_LABELS[k], color: hex(MEMBER_COLORS[k]) }));
}
const present = new Set(all.map((d) => diameterGroupIndex(d.dMm)));
const items = DIAMETER_GROUPS.map((g, i) => ({ label: g.label, color: hex(DIAMETER_COLORS[i]) })).filter((_, i) => present.has(i));
return present.has(-1) ? [...items, { label: '기타', color: hex(OTHER_COLOR) }] : items;
};
return { group, byName, highlight, setColorMode, legend };
}