Revert "feat(rebar): B1–B4와 B5–B8 방향 교환 (B1–B4는 W 방향, B5–B8은 L 방향)"

이 교환은 9,350 방향이 L이라는 가정에서 적용됐으나, 단면 삽도(B5–B8 선, 교각축 직각 단면)와 재료표가 원래 배치(B1–B4 L 방향, B5–B8 W 방향)와 맞는 것으로 확인되어 되돌림.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-27 09:48:05 +09:00
1 parent a026d52799
commit 99db6a1f52
12 files changed
+167 -160

No files matched your search

+50 -37
View File
@@ -6,7 +6,7 @@ import { DEFAULT_FOOTING_REBAR } from '../core/rebar/footing-settings';
/**
* 확대기초 · 교각축 직각 단면 (폭 W) (승인 2026-10-26, 추천안 1·2단계). A cut across the footing width: horizontal = W, vertical = height
* from the 하면 at true scale. Every mark comes from the same functions as the rebar table and the 3D view:
* - B1–B4 orange lines (bars along W, cover to cover), B5–B8 teal points on the grid along W (방향 교환), B9 purple lines per row,
* - B5–B8 teal lines (bars along W), B1–B4 orange points on the grid along W, B9 purple lines per row,
* - B10-1 green points at both W faces per level, B11 grey dashed lines per assembly level, B12 grey points on its W grid.
* Legs are not drawn; the B6 leg length is noted in the figure. The column and the piles are shown as context.
* Each mark carries data-bar="<철근 번호>" so a bubble can highlight its bars (철근표 강조와 같은 방식).
@@ -66,41 +66,44 @@ export function footingSectionSvg(model: PierModel): string {
parts.push(`<rect x="${x(-W / 2).toFixed(1)}" y="${y(H).toFixed(1)}" width="${(W * kX).toFixed(1)}" height="${(H * kY).toFixed(1)}" fill="none" stroke="${COLOR.outline}" stroke-width="1.2"/>`);
parts.push(line('base', '', x(-W / 2), x(W / 2), y(0), COLOR.outline, 1));
// Cover lines on the W faces (측면 피복 c_s).
// B5–B8: teal lines along W between the covers. B5 at the top, B6 and B8 at the 하면 1단, B7 at the 하면 2단.
const xa = x(-W / 2 + fs.cSide);
const xb = x(W / 2 - fs.cSide);
// B1–B4 (방향 교환, 승인 2026-10-26): run along W, so in this section they are lines from cover to cover.
// The B2 legs turn up to the B1 height at both W ends (측면 피복선), drawn heavier.
parts.push(line('b1', 'B1', xa, xb, y(z.B1), COLOR.b14, 1.4));
parts.push(line('b2', 'B2', xa, xb, y(z.B24), COLOR.b14, 1.6));
parts.push(line('b3', 'B3', xa, xb, y(z.B3), COLOR.b14, 1.2));
parts.push(line('b4', 'B4', xa, xb, y(z.B24), COLOR.b14, 1.2));
for (const xx of [xa, xb]) {
parts.push(`<line class="leg" data-bar="B2" x1="${xx.toFixed(1)}" x2="${xx.toFixed(1)}" y1="${y(z.B1).toFixed(1)}" y2="${y(z.B24).toFixed(1)}" stroke="${COLOR.b14}" stroke-width="1.8"/>`);
}
// B6 leg note: the B6 leg runs from the 하면 1단 up to the B5 height (B6 다리 → B5 높이), shown in the right margin below.
parts.push(line('b5', 'B5', xa, xb, y(z.B5), COLOR.b5, 1.6));
parts.push(line('b5', 'B6', xa, xb, y(z.B68), COLOR.b5, 1.2));
parts.push(line('b5', 'B7', xa, xb, y(z.B7), COLOR.b5, 1.2));
parts.push(line('b5', 'B8', xa, xb, y(z.B68), COLOR.b5, 1.2));
// B5–B8 (방향 교환): run along L and are spaced along W (sW). In this section they are points on the W grid:
// odd positions carry B5, B6, B7 and even positions carry B8 (same parity rule as crossBars).
const xsW = gridPositions(W, fs, 'sW');
const last = xsW.length - 1;
xsW.forEach((mm, i) => {
// B1–B4 on the grid along W (교각축, orange points): odd grid positions carry B1, B2 and B3; even carry B4 (as bottomBars).
const xs = gridPositions(W, fs, 'sAxis');
xs.forEach((mm, i) => {
const xx = x(mm);
if (i % 2 === 1) {
parts.push(dot('b5', 'B5', xx, y(z.B5), COLOR.b5));
parts.push(dot('b6', 'B6', xx, y(z.B68), COLOR.b5));
parts.push(dot('b7', 'B7', xx, y(z.B7), COLOR.b5));
parts.push(dot('b1', 'B1', xx, y(z.B1), COLOR.b14));
parts.push(dot('b2', 'B2', xx, y(z.B24), COLOR.b14));
parts.push(dot('b3', 'B3', xx, y(z.B3), COLOR.b14));
} else {
parts.push(dot('b8', 'B8', xx, y(z.B68), COLOR.b5));
parts.push(dot('b4', 'B4', xx, y(z.B24), COLOR.b14));
}
});
// Outermost columns (승인 2026-10-26 추천, 방향 교환 반영): the missing B5–B8 points at the first and last W position are drawn as 'ext' points.
// Outermost columns (승인 2026-10-26, 추천): B1·B2·B3·B4 all sit in the first and last grid column (확대기초철근 (1).md §3, §5).
// The missing ones are drawn as 'ext' points (they do not change the table counts). Their top (B1) and bottom (B2·B4) runs extend
// to the side cover c_s, and a leg line at the cover runs from the 하면 1단 (B2) up to the B1 height.
const xSideL = x(-W / 2 + fs.cSide);
const xSideR = x(W / 2 - fs.cSide);
const last = xs.length - 1;
for (const i of [0, last]) {
const xx = x(xsW[i]);
const drawnHere = i % 2 === 1 ? ['B5', 'B6', 'B7'] : ['B8'];
for (const [name, zz] of [['B5', z.B5], ['B6', z.B68], ['B7', z.B7], ['B8', z.B68]] as const) {
if (!drawnHere.includes(name)) parts.push(dot('ext', name, xx, y(zz), COLOR.b5));
const drawnHere = i % 2 === 1 ? ['B1', 'B2', 'B3'] : ['B4'];
const xx = x(xs[i]);
for (const [name, zz] of [['B1', z.B1], ['B2', z.B24], ['B3', z.B3], ['B4', z.B24]] as const) {
if (!drawnHere.includes(name)) parts.push(dot('ext', name, xx, y(zz), COLOR.b14));
}
const xSide = i === 0 ? xSideL : xSideR;
parts.push(line('ext', 'B1', xx, xSide, y(z.B1), COLOR.b14, 1));
parts.push(line('ext', 'B2', xx, xSide, y(z.B24), COLOR.b14, 1));
// The B2 side leg (up to the B1 height at the side cover) is drawn heavier, so the corner reads as one run (승인 2026-10-26).
parts.push(`<line class="leg" data-bar="B2" x1="${xSide.toFixed(1)}" x2="${xSide.toFixed(1)}" y1="${y(z.B1).toFixed(1)}" y2="${y(z.B24).toFixed(1)}" stroke="${COLOR.b14}" stroke-width="1.8"/>`);
}
// B9: one purple vertical line per row along W (the rows of b9Bars).
@@ -123,12 +126,12 @@ export function footingSectionSvg(model: PierModel): string {
js.forEach((j, idx) => {
const zA = fs.cBottom + j * fs.sV;
parts.push(line('b11', 'B11', xa, xb, y(zA), COLOR.b11, 1.2, '6 3'));
const xsB12 = gridPositions(W, fs, 'sW');
const xsB12 = gridPositions(W, fs, 'sAxis');
for (const i of assemblyGridIndices(xsB12.length - 1, fs.kB12, idx)) parts.push(dot('b12', 'B12', x(xsB12[i]), y(zA + dB10), COLOR.b12, 2.2));
});
// Dimensions: the width line with ticks and the grid string, and the right-hand heights.
const g = grid(W, fs, 'sW');
const g = grid(W, fs, 'sAxis');
const yDim = Y0 + 40;
parts.push(`<line x1="${x(-W / 2).toFixed(1)}" x2="${x(W / 2).toFixed(1)}" y1="${yDim}" y2="${yDim}" stroke="${COLOR.dim}" stroke-width="0.8"/>`);
parts.push(tick(x(-W / 2), yDim) + tick(x(W / 2), yDim));
@@ -138,7 +141,7 @@ export function footingSectionSvg(model: PierModel): string {
parts.push(text(x(-W / 2 + fs.cSide) + 2, yDim + 14, `${g.e.toFixed(1)}`, 'start', COLOR.dim));
parts.push(text(x(W / 2 - fs.cSide) - 2, yDim + 14, `${g.e.toFixed(1)}`, 'end', COLOR.dim));
// The grid string sits right of centre, so the bottom leaders (at the centre and the two sides) do not cross it.
parts.push(text(cx + 150, yDim - 6, `${g.n}@${fs.sW}=${g.G}`, 'middle', COLOR.dim));
parts.push(text(cx + 150, yDim - 6, `${g.n}@${fs.sAxis}=${g.G}`, 'middle', COLOR.dim));
parts.push(text(cx, 20, `교각축 직각 단면 · 폭 ${W.toFixed(0)}`, 'middle', '#1F2937'));
const xr = X0 + W_PX + 18;
parts.push(`<line x1="${xr}" x2="${xr}" y1="${y(H).toFixed(1)}" y2="${y(0).toFixed(1)}" stroke="${COLOR.dim}" stroke-width="0.8"/>`);
@@ -157,22 +160,32 @@ export function footingSectionSvg(model: PierModel): string {
const edgeY = by < ty ? by + 11 : by - 11;
return `<g class="bubble" data-bubble="${name}"><line class="leader" data-bar="${name}" x1="${bx.toFixed(1)}" x2="${tx.toFixed(1)}" y1="${edgeY.toFixed(1)}" y2="${ty.toFixed(1)}" stroke="${COLOR.outline}" stroke-width="0.7"/><circle cx="${bx.toFixed(1)}" cy="${by.toFixed(1)}" r="11" fill="#fff" stroke="${COLOR.outline}"/><text x="${bx.toFixed(1)}" y="${(by + 3).toFixed(1)}" font-size="9" text-anchor="middle" fill="${COLOR.outline}">${name}</text></g>`;
};
const zB11 = js.length > 0 ? fs.cBottom + js[0] * fs.sV : fs.cBottom;
const yTop = 40;
const yBot = Y0 + 100;
// Each bottom bubble sits straight below its own bar (B2 left of centre, B4 at centre, B3 right of centre) on a bar of the right parity.
parts.push(bubble('B5', xa + 12, yTop, x(xsW[1]), y(z.B5)));
parts.push(bubble('B1', cx - 60, yTop, cx - 60, y(z.B1)));
const nearest = (target: number, parity: number) => {
let best = -1;
xs.forEach((mm, i) => {
if (i % 2 === parity && (best < 0 || Math.abs(mm - target) < Math.abs(xs[best] - target))) best = i;
});
return best;
};
const iB2 = nearest(-1930, 1);
const iB4 = nearest(0, 0);
const iB3 = nearest(1930, 1);
const zB11 = js.length > 0 ? fs.cBottom + js[0] * fs.sV : fs.cBottom;
parts.push(bubble('B5', xa + 12, yTop, xa + 12, y(z.B5)));
parts.push(bubble('B1', x(xs[1]) + 60, yTop, x(xs[1]), y(z.B1)));
parts.push(bubble('B11', xb - 20, yTop, xb, y(zB11)));
parts.push(bubble('B10-1', X0 - 18, y(fs.cBottom + (levels - 1) * fs.sV / 2), X0 + 2, y(fs.cBottom + (levels - 1) * fs.sV / 2)));
parts.push(bubble('B2', cx - 70, yBot, cx - 70, y(z.B24)));
parts.push(bubble('B4', cx, yBot, cx, y(z.B24)));
parts.push(bubble('B3', cx + 70, yBot, cx + 70, y(z.B3)));
parts.push(bubble('B2', x(xs[iB2]), yBot, x(xs[iB2]), y(z.B24)));
parts.push(bubble('B4', x(xs[iB4]), yBot, x(xs[iB4]), y(z.B24)));
parts.push(bubble('B3', x(xs[iB3]), yBot, x(xs[iB3]), y(z.B3)));
// Legend (선 모양별, 삽도 기준) and the units note.
const yLeg = 352;
parts.push(`<circle cx="20" cy="${yLeg}" r="2.6" fill="${COLOR.b5}"/>${text(28, yLeg + 3, '주철근 (B5–B8)', 'start', COLOR.outline)}`);
parts.push(`<line x1="180" x2="204" y1="${yLeg}" y2="${yLeg}" stroke="${COLOR.b14}" stroke-width="1.6"/>${text(212, yLeg + 3, '직각·보조 (B1–B4)', 'start', COLOR.outline)}`);
parts.push(`<line x1="20" x2="44" y1="${yLeg}" y2="${yLeg}" stroke="${COLOR.b5}" stroke-width="1.6"/>${text(50, yLeg + 3, '주철근 (B5–B8)', 'start', COLOR.outline)}`);
parts.push(`<circle cx="180" cy="${yLeg}" r="2.6" fill="${COLOR.b14}"/>${text(188, yLeg + 3, '직각·보조 (B1–B4)', 'start', COLOR.outline)}`);
parts.push(`<line x1="330" x2="330" y1="${yLeg - 8}" y2="${yLeg + 8}" stroke="${COLOR.b9}" stroke-width="1"/>${text(338, yLeg + 3, '전단·띠 (B9)', 'start', COLOR.outline)}`);
parts.push(`<circle cx="20" cy="${yLeg + 22}" r="2.2" fill="${COLOR.b10}"/>${text(28, yLeg + 25, '측면 수평 (B10-1 · B10-2)', 'start', COLOR.outline)}`);
parts.push(`<line x1="250" x2="274" y1="${yLeg + 22}" y2="${yLeg + 22}" stroke="${COLOR.b11}" stroke-width="1.2" stroke-dasharray="6 3"/>${text(280, yLeg + 25, '조립 (B11·B12)', 'start', COLOR.outline)}`);
+20 -23
View File
@@ -384,11 +384,10 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
// 확대기초 입력 (승인 2026-10-26): the grid, covers and layer heights come from the model's footing settings.
const fs = m.footingRebar;
const z = layers(Hf, fs);
// 방향 교환 (승인 2026-10-26): B1–B4 run along W and are spaced along L (sL); B5–B8 run along L and are spaced along W (sW).
const gW = grid(Wf * 1000, fs, 'sW');
const gL = grid(Lf * 1000, fs, 'sL');
const posW = (j: number) => -Wf / 2 + mm(fs.cSide + gW.e + j * fs.sW);
const posL = (i: number) => -Lf / 2 + mm(fs.cSide + gL.e + i * fs.sL);
const gW = grid(Wf * 1000, fs);
const gL = grid(Lf * 1000, fs, 'sPerp');
const posW = (j: number) => -Wf / 2 + mm(fs.cSide + gW.e + j * fs.sAxis);
const posL = (i: number) => -Lf / 2 + mm(fs.cSide + gL.e + i * fs.sPerp);
const sA = -Lf / 2 + 0.1;
const sB = Lf / 2 - 0.1;
const nA = -Wf / 2 + 0.1;
@@ -409,28 +408,26 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
const at = (t: number, h: number): [number, number, number] => [f.s + s, h, t];
path(name, legTo === undefined ? [at(nA, y), at(nB, y)] : [at(nA, legTo), at(nA, y), at(nB, y), at(nB, legTo)], dMm);
};
// B1–B4 run along W (at each L position s): odd positions carry B1, B2, B3, even carry B4 (same parity rule as bottomBars).
for (let i = 0; i <= gL.n; i++) {
const sPos = posL(i);
if (i % 2 === 1) {
for (let j = 0; j <= gW.n; j++) {
const n = posW(j);
if (j % 2 === 1) {
// B1 ⊓ down, B2 ⊔ up to the B1 height, B3 ⊔ up. B1·B3 sit beside the B2 leg (+25.4 / +22.25, §5.3).
across('B1', sPos + mm(off.B1), bottom + mm(z.B1), bottom + mm(z.B1) - mm(legLength('S25')), NOMINAL_DIAMETER.S25);
across('B2', sPos, bottom + mm(z.B24), bottom + mm(z.B1), NOMINAL_DIAMETER.S25);
across('B3', sPos + mm(off.B3), bottom + mm(z.B3), bottom + mm(z.B3) + mm(legLength('S19')), NOMINAL_DIAMETER.S19);
along('B1', n + mm(off.B1), bottom + mm(z.B1), bottom + mm(z.B1) - mm(legLength('S25')), NOMINAL_DIAMETER.S25);
along('B2', n, bottom + mm(z.B24), bottom + mm(z.B1), NOMINAL_DIAMETER.S25);
along('B3', n + mm(off.B3), bottom + mm(z.B3), bottom + mm(z.B3) + mm(legLength('S19')), NOMINAL_DIAMETER.S19);
} else {
across('B4', sPos, bottom + mm(z.B24), undefined, NOMINAL_DIAMETER.S25);
along('B4', n, bottom + mm(z.B24), undefined, NOMINAL_DIAMETER.S25);
}
}
// B5–B8 run along L (at each W position n): odd positions carry B5, B6, B7, even carry B8.
for (let j = 0; j <= gW.n; j++) {
const nPos = posW(j);
if (j % 2 === 1) {
for (let i = 0; i <= gL.n; i++) {
const s = posL(i);
if (i % 2 === 1) {
// B5 ⊓ down, B6 ⊔ up to the B5 height, B7 ⊔ up. B5·B7 sit beside the B6 leg (+28.6 / +23.85, §6.3).
along('B5', nPos + mm(off.B5), bottom + mm(z.B5), bottom + mm(z.B5) - mm(legLength('S29')), NOMINAL_DIAMETER.S29);
along('B6', nPos, bottom + mm(z.B68), bottom + mm(z.B5), NOMINAL_DIAMETER.S29);
along('B7', nPos + mm(off.B7), bottom + mm(z.B7), bottom + mm(z.B7) + mm(legLength('S19')), NOMINAL_DIAMETER.S19);
across('B5', s + mm(off.B5), bottom + mm(z.B5), bottom + mm(z.B5) - mm(legLength('S29')), NOMINAL_DIAMETER.S29);
across('B6', s, bottom + mm(z.B68), bottom + mm(z.B5), NOMINAL_DIAMETER.S29);
across('B7', s + mm(off.B7), bottom + mm(z.B7), bottom + mm(z.B7) + mm(legLength('S19')), NOMINAL_DIAMETER.S19);
} else {
along('B8', nPos, bottom + mm(z.B68), undefined, NOMINAL_DIAMETER.S29);
across('B8', s, bottom + mm(z.B68), undefined, NOMINAL_DIAMETER.S29);
}
}
// B9 (확대기초철근 (1).md §9, rule applied): vertical H16 bars on the grid, outside the column zones, with 135° hooks.
@@ -475,8 +472,8 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
}
}
// B11 (across W, spread along L) and B12 (along L, spread across W) at the assembly levels, 4s apart, 2s stagger per level.
const xsL = gridPositions(Lf * 1000, fs, 'sL');
const ysW = gridPositions(Wf * 1000, fs, 'sW');
const xsL = gridPositions(Lf * 1000, fs, 'sPerp');
const ysW = gridPositions(Wf * 1000, fs, 'sAxis');
const dAsm = assemblyEndOffset();
const nAsm = Wf / 2 - mm(d1 + dAsm);
const sAsm = Lf / 2 - mm(d2 + dAsm);