- The coping section listed every piece of a bar as its own crossing, so the same spot and name were drawn many times. Each spot is drawn once and each bar name is written once. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
79 lines
4.0 KiB
TypeScript
79 lines
4.0 KiB
TypeScript
import type { BarRecord } from './rebar3d';
|
||
|
||
/**
|
||
* 코핑 배근 · 정면 and 단면·폐합 (승인 2026-10-26, 추천: 3D 철근 기록을 그대로 투영).
|
||
* 정면: x = the coping length (s), y = EL. 단면: x = 교축 (n, the local z), y = EL, at one station s0: the bars that cross it.
|
||
* Colours by bar group: C1–C8 main, C9–C11 closed loops, C12–C13 sides, C14 points, C15–C16 steps.
|
||
*/
|
||
const COLOR: Record<string, string> = { main: '#0F5C66', loop: '#6B3FA0', side: '#2E7D32', step: '#B4531F', other: '#6B7280' };
|
||
|
||
export function copingBarGroup(name: string): keyof typeof COLOR {
|
||
const n = Number(/^C(\d+)/.exec(name)?.[1]);
|
||
if (!Number.isFinite(n)) return 'other';
|
||
if (n <= 8) return 'main';
|
||
if (n <= 11) return 'loop';
|
||
if (n <= 13) return 'side';
|
||
return 'step';
|
||
}
|
||
|
||
const isCoping = (r: BarRecord) => /^C\d/.test(r.name);
|
||
|
||
/** 정면: the bars seen from the 교축 direction. */
|
||
export function copingFrontSvg(records: BarRecord[], lengthMm: number): string {
|
||
const bars = records.filter(isCoping);
|
||
const els = bars.flatMap((r) => r.pts.map((p) => p[1]));
|
||
const top = Math.max(...els);
|
||
const bottom = Math.min(...els);
|
||
const Y = (el: number) => (top - el) * 1000;
|
||
const parts: string[] = [];
|
||
for (const r of bars) {
|
||
const pts = r.pts.map((p) => `${p[0] * 1000},${Y(p[1])}`).join(' ');
|
||
parts.push(`<polyline points="${pts}" fill="none" stroke="${COLOR[copingBarGroup(r.name)]}" stroke-width="10"></polyline>`);
|
||
}
|
||
parts.push(`<text x="${lengthMm / 2}" y="${-200}" text-anchor="middle" font-size="260" fill="#18212B">코핑 길이 ${lengthMm}</text>`);
|
||
const h = (top - bottom) * 1000;
|
||
return `<svg viewBox="-400 -700 ${lengthMm + 800} ${h + 1000}" width="100%" style="max-width: 760px; height: auto" role="img" aria-label="코핑 배근 정면">${parts.join('')}</svg>`;
|
||
}
|
||
|
||
/**
|
||
* 단면 · 폐합 구성 at the station s0 (m): the bars that cross it, seen along the coping length.
|
||
* A bar crossing s0 is drawn at the crossing point (n, EL); a bar along the 교축 at s0 is drawn as its line.
|
||
*/
|
||
export function copingSectionSvg(records: BarRecord[], s0: number): string {
|
||
const crossings: { name: string; x: number; y: number; group: keyof typeof COLOR }[] = [];
|
||
const bars = records.filter(isCoping);
|
||
const els = bars.flatMap((r) => r.pts.map((p) => p[1]));
|
||
const top = Math.max(...els);
|
||
for (const r of bars) {
|
||
for (let i = 0; i < r.pts.length - 1; i++) {
|
||
const a = r.pts[i];
|
||
const b = r.pts[i + 1];
|
||
const lo = Math.min(a[0], b[0]);
|
||
const hi = Math.max(a[0], b[0]);
|
||
if (s0 < lo || s0 > hi) continue;
|
||
const t = hi - lo < 1e-9 ? 0 : (s0 - a[0]) / (b[0] - a[0]);
|
||
crossings.push({ name: r.name, x: a[2] + (b[2] - a[2]) * t, y: a[1] + (b[1] - a[1]) * t, group: copingBarGroup(r.name) });
|
||
}
|
||
}
|
||
// The same bar is recorded in several pieces, so the same spot can be crossed more than once: draw each spot once,
|
||
// and write each bar name once (at its first crossing), so the labels do not pile up.
|
||
const seen = new Set<string>();
|
||
const named = new Set<string>();
|
||
const unique = crossings.filter((c) => {
|
||
const key = `${Math.round(c.x * 1e4)}|${Math.round(c.y * 1e4)}|${c.name}`;
|
||
if (seen.has(key)) return false;
|
||
seen.add(key);
|
||
return true;
|
||
});
|
||
const xs = unique.map((c) => c.x * 1000);
|
||
const minX = Math.min(...xs, 0);
|
||
const maxX = Math.max(...xs, 0);
|
||
const parts: string[] = unique.map((c) => {
|
||
const dot = `<circle cx="${c.x * 1000}" cy="${(top - c.y) * 1000}" r="${c.group === 'step' ? 22 : 30}" fill="${COLOR[c.group]}"></circle>`;
|
||
if (named.has(c.name)) return dot;
|
||
named.add(c.name);
|
||
return `${dot}<text x="${c.x * 1000 + 60}" y="${(top - c.y) * 1000 - 60}" font-size="120" fill="#18212B">${c.name}</text>`;
|
||
});
|
||
return `<svg viewBox="${minX - 800} -800 ${maxX - minX + 1600} ${(top - Math.min(...els)) * 1000 + 1600}" width="100%" style="max-width: 560px; height: auto" role="img" aria-label="코핑 배근 단면 · 폐합 구성 at s = ${s0} m">${parts.join('')}</svg>`;
|
||
}
|