feat(abut-rebar): ③ 교대 날개벽 3D 철근 (WR1–WR20, 좌 · 우)
- 날개벽 배근 경로(교대 s · t · z)를 교대 3D 철근에 추가. 선택 · 색 기준 공통 - 테스트: 철근표 = 3D 개수 (양측), 벽체 전면 뒤 · 저판 하면망 위 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bac087ce63
commit
e3c9ad0953
3 files changed
+49
-6
No files matched your search
+3
-2
@@ -692,14 +692,15 @@ function setupScene(): void {
|
||||
state.scene = scene;
|
||||
setView(state.view);
|
||||
}
|
||||
// ③ 교대 (2026-11-04): with the piers hidden, the selected abutment's 저판 · 벽체 · 흉벽 rebar is the ③ rebar (표 · 3D 선택 공통).
|
||||
// ③ 교대 (2026-11-04): with the piers hidden, the selected abutment's 저판 · 벽체 · 흉벽 · 날개벽 rebar is the ③ rebar (표 · 3D 선택 공통).
|
||||
const abutRec = piersShown(state.stage, state.abutActive) ? undefined : state.abutments.find((a) => a.name === state.abutActive);
|
||||
const abutGroup = abutRec ? abutGroupByName.get(abutRec.name) : undefined;
|
||||
const footing = abutRec ? abutFootingBuilt(abutRec) : null;
|
||||
if (abutRec && abutGroup && footing && footing.bottomEl !== undefined) {
|
||||
const wall = abutWallBuilt(abutRec, footing.result);
|
||||
const para = wall ? abutParapetBuilt(abutRec, wall) : null;
|
||||
rebar3d = buildAbutRebar3D(footing.geom, footing.result, footing.skewK, footing.backM, footing.bottomEl, wall?.result, para?.result);
|
||||
const wings = abutWingsBuilt(abutRec, footing.result).map((w) => w.result);
|
||||
rebar3d = buildAbutRebar3D(footing.geom, footing.result, footing.skewK, footing.backM, footing.bottomEl, wall?.result, para?.result, wings);
|
||||
rebar3d.group.applyMatrix4(abutGroup.matrix);
|
||||
rebar3d.group.visible = state.stage === 'rebar';
|
||||
rebar3d.setColorMode(state.rebarColor);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 교대 3D 철근 (③, 2026-11-04): 저판 B1–B10, 벽체 W1–W11, 흉벽 · 브라켓 P1–P11. Positions come from `abutFootingRebar` ·
|
||||
* 교대 3D 철근 (③, 2026-11-04): 저판 B1–B10, 벽체 W1–W11, 흉벽 · 브라켓 P1–P11, 날개벽 WR1–WR20 (좌 · 우). Positions come from `abutFootingRebar` ·
|
||||
* `abutWallRebar` · `abutParapetRebar` (no new rules here; the 벽체 · 흉벽 bars are their paths). The 저판 shapes follow the
|
||||
* 배근 삽도 (abut-footing-rebar-svg.ts): B1 ⊓ / B2 ⊔ / B3 · B4 직선, B5 ⊓ / B6 ⊔ / B7 · B8 직선, B9 연직, B10 측면, B1-S · B2-S.
|
||||
* Hooks and the bend radii are not drawn. Each bar is named by its 철근표 number, so the table, the 삽도 and the model
|
||||
@@ -12,6 +12,8 @@ import { memberOfBar } from '../core/rebar/color';
|
||||
import { ABUT_WALL_SIZES, type AbutWallRebar } from '../core/abut/rebar/wall';
|
||||
import { ABUT_PARAPET_SIZES, type AbutParapetRebar } from '../core/abut/rebar/parapet';
|
||||
import { ABUT_PARAPET_COLORS } from './abut-parapet-rebar-svg';
|
||||
import { ABUT_WING_SIZES, type AbutWingRebar } from '../core/abut/rebar/wing';
|
||||
import { ABUT_WING_COLORS } from './abut-wing-rebar-svg';
|
||||
import { ABUT_FOOTING_COLORS } from './abut-footing-rebar-svg';
|
||||
import { ABUT_WALL_COLORS } from './abut-wall-rebar-svg';
|
||||
import { rebarColor, rebarControls, type RebarScene } from './rebar3d';
|
||||
@@ -20,14 +22,23 @@ type P3 = [number, number, number];
|
||||
|
||||
/**
|
||||
* @param skewK plan skew (s per m of t, `abutSkewK`), @param backM 저판 배면 t (m), @param bottomEl 기초 하단 EL (m),
|
||||
* @param wall 벽체 배근, @param parapet 흉벽 배근 (없으면 그 부재는 그리지 않음).
|
||||
* @param wall 벽체 배근, @param parapet 흉벽 배근, @param wings 날개벽 배근 (측마다, 경로는 교대 s · t · z) — 없으면 그 부재는 그리지 않음.
|
||||
*/
|
||||
export function buildAbutRebar3D(g: AbutFootingGeom, r: AbutFootingRebar, skewK: number, backM: number, bottomEl: number, wall?: AbutWallRebar, parapet?: AbutParapetRebar): RebarScene {
|
||||
export function buildAbutRebar3D(
|
||||
g: AbutFootingGeom,
|
||||
r: AbutFootingRebar,
|
||||
skewK: number,
|
||||
backM: number,
|
||||
bottomEl: number,
|
||||
wall?: AbutWallRebar,
|
||||
parapet?: AbutParapetRebar,
|
||||
wings: AbutWingRebar[] = [],
|
||||
): RebarScene {
|
||||
const group = new THREE.Group();
|
||||
group.name = 'abut-rebar';
|
||||
const byName = new Map<string, THREE.Mesh[]>();
|
||||
const C = Object.fromEntries(Object.entries(ABUT_FOOTING_COLORS).map(([k, v]) => [k, parseInt(v.slice(1), 16)])) as Record<keyof typeof ABUT_FOOTING_COLORS, number>;
|
||||
const sizeOf = new Map<string, keyof typeof NOMINAL_DIAMETER>([...r.bars.map((b) => [b.mark, b.size] as const), ...Object.entries(ABUT_WALL_SIZES), ...Object.entries(ABUT_PARAPET_SIZES)]);
|
||||
const sizeOf = new Map<string, keyof typeof NOMINAL_DIAMETER>([...r.bars.map((b) => [b.mark, b.size] as const), ...Object.entries(ABUT_WALL_SIZES), ...Object.entries(ABUT_PARAPET_SIZES), ...Object.entries(ABUT_WING_SIZES)]);
|
||||
// (s, x, z) in mm: s along the skew grid (끝면과 나란히), x from the 배면, z from the 저판 하면.
|
||||
const P = (s: number, x: number, z: number): P3 => [s / 1000 + skewK * (backM + x / 1000), bottomEl + z / 1000, backM + x / 1000];
|
||||
// Option ②: square to the axis, s in the 삽도's frame (abutFootingRebarPlanSvg SQ), the same for every x.
|
||||
@@ -116,5 +127,9 @@ export function buildAbutRebar3D(g: AbutFootingGeom, r: AbutFootingRebar, skewK:
|
||||
['P1', 'P2', 'P3', 'P11'].includes(mark) ? P.vertical : mark === 'P6' || mark === 'P7' ? P.tie : ['P8', 'P9', 'P10'].includes(mark) ? P.side : P.horizontal;
|
||||
for (const p of parapet.paths) add(p.mark, p.pts.map(toScene), colorOf(p.mark));
|
||||
}
|
||||
const G = Object.fromEntries(Object.entries(ABUT_WING_COLORS).map(([k, v]) => [k, parseInt(v.slice(1), 16)])) as Record<keyof typeof ABUT_WING_COLORS, number>;
|
||||
const wingColor = (mark: string) =>
|
||||
['WR1', 'WR2', 'WR11', 'WR12'].includes(mark) ? G.vertical : /^WR(3|4|5|13|14|15)$/.test(mark) ? G.horizontal : mark === 'WR6' || mark === 'WR20' ? G.other : G.haunch;
|
||||
for (const w of wings) for (const p of w.paths) add(p.mark, p.pts.map(([s, t, z]): P3 => [s / 1000, bottomEl + z / 1000, t / 1000]), wingColor(p.mark));
|
||||
return { group, byName, records: [], ...rebarControls(byName) };
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import { abutSkewK } from '../src/view/abut-scene';
|
||||
import { buildAbutRebar3D } from '../src/view/abut-rebar3d';
|
||||
import { abutWallGeom, abutWallRebar } from '../src/core/abut/rebar/wall';
|
||||
import { abutParapetGeom, abutParapetRebar } from '../src/core/abut/rebar/parapet';
|
||||
import { abutWingGeom, abutWingRebar } from '../src/core/abut/rebar/wing';
|
||||
import { buildWings } from '../src/core/abut/wings';
|
||||
|
||||
// 예제 A1 (교대확대기초철근.md §13).
|
||||
const RAG = fileURLToPath(new URL('../../../RAG/운곡교P1입력.xlsx', import.meta.url));
|
||||
@@ -131,3 +133,28 @@ describe('교대 흉벽 · 브라켓 3D 철근 (③, 2026-11-04)', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('교대 날개벽 3D 철근 (③, 2026-11-04)', () => {
|
||||
const footing = abutFootingRebar(geom);
|
||||
const wingsR = buildWings(model, members).map((w) => abutWingRebar(abutWingGeom(model, members, w, footing.z.B68)));
|
||||
const scene = buildAbutRebar3D(geom, footing, k, back, bottom, undefined, undefined, wingsR);
|
||||
|
||||
it('draws both wings: every WR bar (one tube per straight piece), 좌 + 우', () => {
|
||||
expect(wingsR.length).toBe(2);
|
||||
const marks = new Set(wingsR.flatMap((r) => r.bars.map((b) => b.mark)));
|
||||
for (const m of marks) {
|
||||
const pieces = wingsR.flatMap((r) => r.paths.filter((p) => p.mark === m)).reduce((a, p) => a + p.pts.length - 1, 0);
|
||||
expect(scene.byName.get(m)?.length, m).toBe(pieces);
|
||||
}
|
||||
});
|
||||
|
||||
it('wing bars lie behind the 벽체 전면 and above the 저판 하면망', () => {
|
||||
const box = new THREE.Box3();
|
||||
for (const r of wingsR) for (const b of r.bars) for (const mesh of scene.byName.get(b.mark)!) {
|
||||
mesh.geometry.computeBoundingBox();
|
||||
box.copy(mesh.geometry.boundingBox!);
|
||||
expect(box.max.z, b.mark).toBeLessThan(members.shape.wallFrontM);
|
||||
expect(box.min.y, b.mark).toBeGreaterThan(bottom + footing.z.B68 / 1000 - (b.mark === 'WR6' ? 0.8 : 0.4));
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user