fix(abut-rebar): 날개벽 WR7-1 · 7-2 · 7-3 평면 형상 (긴 대각 + 양끝 갈고리 394)
- 사용자 도면(2026-11-05): 헌치 빗변과 나란한 대각이 안쪽 모서리를 c 떨어져 지나, 교대 쪽은 전면 − c (WR7-1 · 7-2 흉벽 전면, WR7-3 벽체 전면), 날개벽 쪽은 날개벽 바깥면 − c에서 끝남. 양끝 평면 90° 갈고리 394 (교대 안쪽 / 날개벽 끝 쪽) - WR7-3의 긴 대각이 더 낮은 이웃 계단의 교좌면 위로 나오는 단은 WR7-2로 (교좌면 계단을 날개벽 형상에 전달) - 테스트: 갈고리 394 · 끝점 · 대각 길이, WR7-1 · 7-2 끝, 우측 재분류, 콘크리트 안 검사 통과 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
89ebf852a9
commit
7b9de649e5
5 files changed
+86
-31
No files matched your search
+45
-27
@@ -74,6 +74,9 @@ export interface AbutWingGeom {
|
||||
bracketTop: number;
|
||||
/** 저판 하면망 B6 · B8 높이 (z). */
|
||||
zB68: number;
|
||||
/** 교좌면 계단 (u = s − k·t 범위 mm, 교좌면 z): WR7-3이 긴 대각으로 이웃 계단까지 갈 때 그 교좌면 아래인지 본다. 없으면 seat 하나. */
|
||||
seats?: { from: number; to: number; z: number }[];
|
||||
skewK?: number;
|
||||
}
|
||||
|
||||
export type WingRole = '연직' | '수평' | '헌치' | '모서리' | '간격재' | '경사 하단';
|
||||
@@ -154,6 +157,8 @@ export function abutWingGeom(
|
||||
bands: { name: string; zBottomEl: number }[];
|
||||
},
|
||||
zB68: number,
|
||||
seats?: { from: number; to: number; z: number }[],
|
||||
skewK?: number,
|
||||
): AbutWingGeom {
|
||||
const front = { e: model.nLeft.e * model.frontSide, n: model.nLeft.n * model.frontSide };
|
||||
const st = (p: { e: number; n: number }): V2 => {
|
||||
@@ -193,6 +198,8 @@ export function abutWingGeom(
|
||||
seat: seatBand ? z(wing.bands.find((b) => b.name === '변화단면1')!.zBottomEl) : z(members.footingTopEl),
|
||||
bracketTop: z(wing.topStartEl - members.shape.bracketHeightM),
|
||||
zB68,
|
||||
seats,
|
||||
skewK,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -368,34 +375,44 @@ export function abutWingRebar(g: AbutWingGeom, changes?: Partial<AbutWingRebarSe
|
||||
};
|
||||
if (g.haunch > 0) {
|
||||
const sp = 2 * sBackV;
|
||||
const legIn = T - c - (d('H16') + d('H25')) / 2 - (d('H25') + d('H25')) / 2; // WR3–5 안쪽 25.4
|
||||
for (let k = 0; ; k++) {
|
||||
const z = g.footTop + c + k * sp;
|
||||
if (z > g.topStart - c + 1e-6) break;
|
||||
const mark: WingMark = z > g.bracketTop + 1e-6 ? 'WR7-1' : z > g.seat + 1e-6 ? 'WR7-2' : 'WR7-3';
|
||||
let mark: WingMark = z > g.bracketTop + 1e-6 ? 'WR7-1' : z > g.seat + 1e-6 ? 'WR7-2' : 'WR7-3';
|
||||
// 평면 (사용자 도면 2026-11-05): 헌치 빗변과 나란한 긴 대각이 안쪽 모서리(날개벽 배면 × 교대 배면)를 콘크리트 쪽으로 c 떨어져
|
||||
// 지나, 교대 쪽은 전면 − c 선(WR7-1 · 7-2 흉벽 전면, WR7-3 벽체 전면), 날개벽 쪽은 날개벽 바깥면 − c 선에서 끝난다.
|
||||
// 양끝은 평면 안 90° 갈고리 394: 교대 쪽은 전면 선을 따라 교대 안쪽으로, 날개벽 쪽은 바깥면 선을 따라 날개벽 끝 쪽으로.
|
||||
const plan = (m: WingMark) => {
|
||||
const backT = m === 'WR7-1' ? g.parapetBackT : g.wallBackT;
|
||||
const tEnd = m === 'WR7-3' ? g.wallFrontT - c : -c;
|
||||
const corner = lineX(at(0, T), wDir, [0, backT], [1, 0]);
|
||||
const hw = add2(corner, wDir, g.haunch);
|
||||
const ha = add2(corner, uOut, g.haunch);
|
||||
const dl = Math.hypot(ha[0] - hw[0], ha[1] - hw[1]);
|
||||
const du: V2 = [(ha[0] - hw[0]) / dl, (ha[1] - hw[1]) / dl];
|
||||
const away: V2 = [corner[0] - (hw[0] + ha[0]) / 2, corner[1] - (hw[1] + ha[1]) / 2];
|
||||
const al = Math.hypot(away[0], away[1]);
|
||||
const L0 = add2(corner, [away[0] / al, away[1] / al], c);
|
||||
const F = lineX(L0, du, [0, tEnd], [1, 0]);
|
||||
const G = lineX(L0, du, at(0, c), wDir);
|
||||
const legG = Math.min(h25, legEnd(G, z));
|
||||
const len = h25 + Math.hypot(G[0] - F[0], G[1] - F[1]) + legG;
|
||||
return { pts: [add2(F, uOut, h25), F, G, add2(G, wDir, legG)] as V2[], len };
|
||||
};
|
||||
let bar = plan(mark);
|
||||
// WR7-3의 긴 대각이 이웃 계단(교좌면이 더 낮음)까지 가면 그 교좌면 위로 나온다 → 그 단은 WR7-2 (흉벽 전면 − c, 브라켓 안).
|
||||
if (mark === 'WR7-3' && g.seats) {
|
||||
const seatAt = (q: V2) => {
|
||||
const u = q[0] - (g.skewK ?? 0) * q[1];
|
||||
return (g.seats!.find((x) => u < x.to) ?? g.seats![g.seats!.length - 1]).z;
|
||||
};
|
||||
if (bar.pts.some((q) => z > seatAt(q) + 1e-6)) {
|
||||
mark = 'WR7-2';
|
||||
bar = plan(mark);
|
||||
}
|
||||
}
|
||||
haunchLevels[mark]++;
|
||||
const backT = mark === 'WR7-1' ? g.parapetBackT : g.wallBackT;
|
||||
const tEnd = mark === 'WR7-3' ? g.wallFrontT - c : -c;
|
||||
// 모서리 = 배면 선 ∩ 교대 배면 선; 헌치 대각은 날개벽 쪽 점 ~ 교대 쪽 점, 철근은 그 안쪽 c.
|
||||
const backFace = at(0, T);
|
||||
const corner = lineX(backFace, wDir, [0, backT], [1, 0]);
|
||||
const hw = add2(corner, wDir, g.haunch);
|
||||
const ha = add2(corner, uOut, g.haunch);
|
||||
const dv: V2 = [ha[0] - hw[0], ha[1] - hw[1]];
|
||||
const dl = Math.hypot(dv[0], dv[1]);
|
||||
const du: V2 = [dv[0] / dl, dv[1] / dl];
|
||||
const toCorner: V2 = [corner[0] - hw[0], corner[1] - hw[1]];
|
||||
const nrm: V2 = [-du[1], du[0]];
|
||||
const sgn = nrm[0] * toCorner[0] + nrm[1] * toCorner[1] > 0 ? 1 : -1;
|
||||
const diagP = add2(hw, nrm, sgn * c);
|
||||
const legLine = at(0, legIn);
|
||||
const A = lineX(legLine, wDir, diagP, du);
|
||||
const B = lineX(diagP, du, [0, backT + c], [1, 0]);
|
||||
const leg = legEnd(A, z);
|
||||
const P0 = add2(A, wDir, leg);
|
||||
const C: V2 = [B[0], tEnd];
|
||||
const len = leg + Math.hypot(B[0] - A[0], B[1] - A[1]) + Math.abs(tEnd - B[1]) + h25;
|
||||
put(mark, [P3(P0, z), P3(A, z), P3(B, z), P3(C, z), P3(C, z - h25)], len);
|
||||
put(mark, bar.pts.map((q) => P3(q, z)), bar.len);
|
||||
}
|
||||
}
|
||||
// WR9 (사용자 도면 2026-11-04, 연직헌치 보강철근): 저판 상면 헌치(날개벽 배면 ~ 저판 상면, 다리 g.haunch) 보강.
|
||||
@@ -485,9 +502,10 @@ export function abutWingRebar(g: AbutWingGeom, changes?: Partial<AbutWingRebarSe
|
||||
push({ mark: 'WR4', size: SZ.WR4, role: '수평', spacing: hz1, actual: hz1, shape: `흉벽 + 브라켓 지나 90° 갈고리 ${h25}`, position: '배면 수평 · 변화단면1', note: '', basis: '규칙', modified: hMod });
|
||||
push({ mark: 'WR5', size: SZ.WR5, role: '수평', spacing: hz1, actual: hz1, shape: `벽체 지나 90° 갈고리 ${h25}`, position: '배면 수평 · 변화단면2', note: '', basis: '규칙', modified: hMod });
|
||||
push({ mark: 'WR6', size: SZ.WR6, role: '경사 하단', spacing: '—', actual: '—', shape: `경사 하단과 나란히 · 아래 저판 안 l_d ${ld.H16}`, position: '경사 하단 · 전면 · 배면 각 1', note: '"cycle-2" 삭제 (결정)', basis: '규칙', modified: false });
|
||||
push({ mark: 'WR7-1', size: SZ['WR7-1'], role: '헌치', spacing: hz2, actual: hz2, shape: `꺾임 · 날개벽 l_d ${ld.H25} · 헌치 대각 · 흉벽 지나 갈고리`, position: '위 헌치 · 브라켓 상면 ~ 상단', note: '날개벽 다리 WR3–5 안쪽 25.4 (Q-WR7)', basis: '규칙', modified: hMod });
|
||||
push({ mark: 'WR7-2', size: SZ['WR7-2'], role: '헌치', spacing: hz2, actual: hz2, shape: '꺾임 · 흉벽 + 브라켓 지나 갈고리', position: '아래 헌치 윗부분 · 교좌면 ~ 브라켓 상면', note: '띠 경계 브라켓 상면 / 교좌면 (Q-WR6)', basis: '규칙', modified: hMod });
|
||||
push({ mark: 'WR7-3', size: SZ['WR7-3'], role: '헌치', spacing: hz2, actual: hz2, shape: '꺾임 · 벽체 지나 갈고리', position: '아래 헌치 아랫부분 · 저판 상면 ~ 교좌면', note: 'WR8 결번', basis: '규칙', modified: hMod });
|
||||
const wr7Shape = (front: string) => `평면 꺾임 · 갈고리 ${h25} · 안쪽 모서리 c 지나는 대각 (헌치 빗변과 나란히) · ${front} − c ~ 날개벽 바깥면 − c · 갈고리 ${h25}`;
|
||||
push({ mark: 'WR7-1', size: SZ['WR7-1'], role: '헌치', spacing: hz2, actual: hz2, shape: wr7Shape('흉벽 전면'), position: '위 헌치 · 브라켓 상면 ~ 상단', note: '사용자 도면 2026-11-05: 긴 대각 + 양끝 평면 갈고리', basis: '규칙', modified: hMod });
|
||||
push({ mark: 'WR7-2', size: SZ['WR7-2'], role: '헌치', spacing: hz2, actual: hz2, shape: wr7Shape('흉벽 전면'), position: '아래 헌치 윗부분 · 교좌면 ~ 브라켓 상면', note: '띠 경계 브라켓 상면 / 교좌면 (Q-WR6)', basis: '규칙', modified: hMod });
|
||||
push({ mark: 'WR7-3', size: SZ['WR7-3'], role: '헌치', spacing: hz2, actual: hz2, shape: wr7Shape('벽체 전면'), position: '아래 헌치 아랫부분 · 저판 상면 ~ 교좌면', note: 'WR8 결번', basis: '규칙', modified: hMod });
|
||||
push({ mark: 'WR9', size: SZ.WR9, role: '헌치', spacing: lenText(2 * sBack), actual: lenText(2 * sBack), shape: `꺾임 · 연직 다리 ${lenText(la9)} · 헌치 빗변과 나란한 대각 · 저판 하면망에서 90° 갈고리 ${h16}`, position: '저판 상면 헌치 (날개벽 배면) · 벽체 배면 ~ 뒷굽 끝', note: `연직헌치 보강 (사용자 도면 2026-11-04) · @${lenText(2 * sBack)} 자동 개수 · 연직 다리 = 벽체 W9 · W10 정착장`, basis: '규칙', modified: vMod });
|
||||
push({ mark: 'WR10', size: SZ.WR10, role: '모서리', spacing: hz1, actual: hz1, shape: `꺾임 · 모서리 대각 300 · 양끝 l_d ${ld.H25}`, position: '벽체 · 흉벽 배면 ↔ 날개벽 배면 모서리 · 저판 상면 ~ 흉벽 상단', note: '헌치 철근과의 간섭은 보지 않음 (Q-WR4)', basis: '규칙', modified: hMod });
|
||||
push({ mark: 'WR20', size: SZ.WR20, role: '간격재', spacing: `${lenText(s.k20 * sBack)} × ${lenText(s.k20 * sBackV)}`, actual: '—', shape: `양끝 90° 갈고리 ${hook(SZ.WR20)}`, position: `내측 간격재 · 두께 ${lenText(T - 2 * (c + (d('H16') + d('H25')) / 2))} 걸침`, note: '철근표 H20은 오기 → H13', basis: '규칙', modified: s.k20 !== 8 });
|
||||
|
||||
+3
-1
@@ -3163,8 +3163,10 @@ function abutWingsBuilt(rec: AbutRecord, footing: AbutFootingRebar): { geom: Abu
|
||||
const model = buildAbut(toAbutInput(rec));
|
||||
const members = buildMembers(model, { bottomEl: rec.foundationBottomEl, pileLengthMm: rec.pileLengthMm }, rec.shape);
|
||||
const material = materialFor(state.material, state.fckByMember, 'abutBody');
|
||||
const zOf = (el: number) => (el - (members.footingBottomEl ?? 0)) * 1000;
|
||||
const seats = members.wallSegments.map((sg) => ({ from: sg.sFrom * 1000, to: sg.sTo * 1000, z: zOf(sg.seatEl) }));
|
||||
return buildWings(model, members, rec.wings, rec.wingDims).map((w) => {
|
||||
const geom = abutWingGeom(model, members, w, footing.z.B68);
|
||||
const geom = abutWingGeom(model, members, w, footing.z.B68, seats, abutSkewK(model));
|
||||
// WR9 연직 다리 = 벽체 W9 · W10 정착장 (사용자 결정 2026-11-04).
|
||||
return { geom, result: abutWingRebar(geom, rec.rebar?.wing, material, rec.name, resolveWallRebar(rec.rebar?.wall).la) };
|
||||
});
|
||||
|
||||
@@ -37,7 +37,8 @@ const res: Record<string, { out: number; total: number }> = {};
|
||||
const span = slabSpanAt(abutPlanOutline(model, members, wings, slab).slab, tM, k)!;
|
||||
const pg = abutParapetGeom(wg, members.shape, model.bearings[0].s, { B: model.wing!.slabWidthMm, a: span.a, b: span.b, nearT: members.shape.parapetBackM * 1000 - slab.parapetGapMm, chamferA: 300, chamferB: 300 });
|
||||
const para = abutParapetRebar(pg, wall);
|
||||
const wingR = wings.map((w) => abutWingRebar(abutWingGeom(model, members, w, footing.z.B68)));
|
||||
const seats = members.wallSegments.map((sg) => ({ from: sg.sFrom * 1000, to: sg.sTo * 1000, z: (sg.seatEl - members.footingBottomEl!) * 1000 }));
|
||||
const wingR = wings.map((w) => abutWingRebar(abutWingGeom(model, members, w, footing.z.B68, seats, k)));
|
||||
const rebar = buildAbutRebar3D(fg, footing, k, members.shape.footingBackM, members.footingBottomEl!, wall, para, wingR);
|
||||
const group = buildAbutGroup(model, members, wings);
|
||||
group.updateMatrixWorld(true);
|
||||
|
||||
@@ -10,6 +10,7 @@ import { abutFootingGeom, abutFootingRebar } from '../src/core/abut/rebar/footin
|
||||
import { skewAngle } from '../src/core/abut/rebar/skew-layout';
|
||||
import { abutWingGeom, abutWingRebar, wingLevelCount } from '../src/core/abut/rebar/wing';
|
||||
import { DEFAULT_MATERIAL } from '../src/core/rebar/common';
|
||||
import { abutSkewK } from '../src/view/abut-scene';
|
||||
|
||||
// 예제 A1 좌측 날개벽 (교대날개벽철근.md §11). 기획서 값은 fck 27 · 벽체 높이 2500 기준 (흉벽 상단 46.835 / 47.180).
|
||||
const RAG = fileURLToPath(new URL('../../../RAG/운곡교P1입력.xlsx', import.meta.url));
|
||||
@@ -19,7 +20,8 @@ const members = buildMembers(model, { bottomEl: rec.foundationBottomEl, pileLeng
|
||||
const wings = buildWings(model, members);
|
||||
const footing = abutFootingRebar(abutFootingGeom(model.length, members.shape, skewAngle(rec.skewDeg)));
|
||||
const fck27 = { ...DEFAULT_MATERIAL, fck: 27 };
|
||||
const geomOf = (side: 'left' | 'right') => abutWingGeom(model, members, wings.find((w) => w.side === side)!, footing.z.B68);
|
||||
const seats = members.wallSegments.map((sg) => ({ from: sg.sFrom * 1000, to: sg.sTo * 1000, z: (sg.seatEl - members.footingBottomEl!) * 1000 }));
|
||||
const geomOf = (side: 'left' | 'right') => abutWingGeom(model, members, wings.find((w) => w.side === side)!, footing.z.B68, seats, abutSkewK(model));
|
||||
const left = geomOf('left');
|
||||
const L1 = abutWingRebar(left, undefined, fck27, 'A1');
|
||||
const bar = (r: typeof L1, mark: string) => r.bars.find((b) => b.mark === mark);
|
||||
@@ -137,3 +139,34 @@ describe('③ 날개벽 삽도', () => {
|
||||
for (const m of ['WR7', 'WR10', 'WR9']) expect(svg).toContain(`data-pick="${m}"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WR7 평면 형상 (사용자 도면 2026-11-05)', () => {
|
||||
it('WR7-3: 벽체 전면 − c에서 교대 안쪽으로 갈고리 394 → 안쪽 모서리를 c 떨어져 지나는 긴 대각 → 날개벽 바깥면 − c에서 날개벽 쪽 갈고리 394', () => {
|
||||
const p = L1.paths.find((q) => q.mark === 'WR7-3')!;
|
||||
expect(p.pts.length).toBe(4);
|
||||
const [h, F, G, e] = p.pts;
|
||||
expect(F[1]).toBeCloseTo(left.wallFrontT - 100, 6);
|
||||
expect(h[1]).toBeCloseTo(F[1], 6); // 갈고리는 전면 선을 따라
|
||||
expect(Math.hypot(h[0] - F[0], h[1] - F[1])).toBeCloseTo(394, 6);
|
||||
// G는 날개벽 바깥면에서 c 안쪽.
|
||||
const [ws, wt] = left.w;
|
||||
const iw = left.inward[0] * ws + left.inward[1] * wt;
|
||||
const nv = [left.inward[0] - iw * ws, left.inward[1] - iw * wt];
|
||||
const T = Math.hypot(nv[0], nv[1]);
|
||||
const depth = ((G[0] - left.p0[0]) * nv[0] + (G[1] - left.p0[1]) * nv[1]) / T;
|
||||
expect(depth).toBeCloseTo(100, 6);
|
||||
expect(Math.hypot(e[0] - G[0], e[1] - G[1])).toBeCloseTo(394, 6);
|
||||
// 긴 대각: 벽체 두께를 가로지름 (3 m 이상).
|
||||
expect(Math.hypot(G[0] - F[0], G[1] - F[1])).toBeGreaterThan(3000);
|
||||
});
|
||||
|
||||
it('WR7-1 · 7-2는 흉벽 전면 − c에서 끝남', () => {
|
||||
for (const m of ['WR7-1', 'WR7-2']) expect(L1.paths.find((q) => q.mark === m)!.pts[1][1]).toBeCloseTo(-100, 6);
|
||||
});
|
||||
|
||||
it('우측: 긴 대각이 더 낮은 이웃 계단까지 가는 단은 WR7-3 대신 WR7-2 (교좌면 위로 나오지 않게)', () => {
|
||||
const R = abutWingRebar(geomOf('right'), undefined, fck27);
|
||||
const total = R.haunchLevels['WR7-1'] + R.haunchLevels['WR7-2'] + R.haunchLevels['WR7-3'];
|
||||
expect(R.haunchLevels['WR7-3']).toBeLessThan(total - R.haunchLevels['WR7-1'] - 6);
|
||||
});
|
||||
});
|
||||
@@ -37,7 +37,8 @@ it('dump 콘크리트 밖 철근', () => {
|
||||
const span = slabSpanAt(abutPlanOutline(model, members, wings, slab).slab, tM, k)!;
|
||||
const pg = abutParapetGeom(wg, members.shape, model.bearings[0].s, { B: model.wing!.slabWidthMm, a: span.a, b: span.b, nearT: members.shape.parapetBackM * 1000 - slab.parapetGapMm, chamferA: 300, chamferB: 300 });
|
||||
const para = abutParapetRebar(pg, wall);
|
||||
const wingR = wings.map((w) => abutWingRebar(abutWingGeom(model, members, w, footing.z.B68)));
|
||||
const seats = members.wallSegments.map((sg) => ({ from: sg.sFrom * 1000, to: sg.sTo * 1000, z: (sg.seatEl - members.footingBottomEl!) * 1000 }));
|
||||
const wingR = wings.map((w) => abutWingRebar(abutWingGeom(model, members, w, footing.z.B68, seats, k)));
|
||||
const rebar = buildAbutRebar3D(fg, footing, k, members.shape.footingBackM, members.footingBottomEl!, wall, para, wingR);
|
||||
const group = buildAbutGroup(model, members, wings);
|
||||
group.updateMatrixWorld(true);
|
||||
|
||||
Reference in new issue
Block a user