feat(abut): 벽체 높이 H_w = 받침 ① 교좌면 ~ 흉벽 상단 연직 (기본 3326)
- 사용자 도면(2026-11-04): 받침 ① 위치에서 받침 ① 교좌면부터 흉벽 상단까지 연직 거리. 모든 교대 기본값 3326 (QA-4 가정 2500 대체) - 입력 이름 "벽체 높이 · 받침 ① 기준 연직", ② 정면도에 받침 ① 기준선과 H_w 치수 - ③ 흉벽 철근표 대표 단면 = 받침 ① 위치 (P1–P3는 그 단면에서 정확히 계산) - 테스트: 흉벽 상단 IP1 48.006 / IP3 47.661 등 + 0.826 반영, 교대흉벽철근.md §11 재현은 2500을 넣어 유지, 3326 기본 확인 (P1 3945) Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
51a36669a8
commit
5e64a4f2ed
17 files changed
+125
-71
No files matched your search
@@ -214,7 +214,7 @@ export function applySlabEdit(rec: AbutRecord, key: (typeof SLAB_EDIT_FIELDS)[nu
|
||||
export const SHAPE_EDIT_FIELDS: { key: keyof AbutShape; label: string; group: 'wall' | 'footing'; note?: string }[] = [
|
||||
// 벽체 · 흉벽 (시안 입력 격자 순서). 벽체 두께는 자동 = 브라켓 폭 + 흉벽 두께 + 교좌면 폭.
|
||||
{ key: 'wallFrontMm', label: '교좌면 폭 (mm)', group: 'wall' },
|
||||
{ key: 'parapetHeightMm', label: '흉벽 높이 (mm)', group: 'wall' },
|
||||
{ key: 'parapetHeightMm', label: '벽체 높이 · 받침 ① 기준 연직 (mm)', group: 'wall', note: '받침 ① 교좌면 ~ 흉벽 상단' },
|
||||
{ key: 'parapetThicknessMm', label: '흉벽 두께 (mm)', group: 'wall' },
|
||||
{ key: 'bracketHeightMm', label: '브라켓 높이 (mm)', group: 'wall', note: '= 접속슬래브 카드 흉벽 높이' },
|
||||
{ key: 'bracketWidthMm', label: '브라켓 폭 (mm)', group: 'wall' },
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* 교대 부재 · AA3 (2026-11-03, spec_abut §4.3–4.6): 벽체, 흉벽 상단, 저판, 말뚝의 치수와 위치. Pure.
|
||||
*
|
||||
* - 교대축 기준 t: 전면 쪽이 +. 벽체 전면 +1.6 m (교좌면 끝), 배면 −0.8 m (브라켓 배면). 두께 2.4 m.
|
||||
* - 흉벽: t −0.5 ~ 0 m. 상단 EL(s) = 교좌면 EL(구간 ①) + 흉벽 높이 2.5 m + i_b × (s − s_1), i_b = 받침 z의 축 방향 경사.
|
||||
* - 흉벽: t −0.5 ~ 0 m. 상단 EL(s) = 받침 ① 교좌면 EL + 벽체 높이 H_w (받침 ① 기준 연직, 기본 3.326 m) + i_b × (s − s_1), i_b = 받침 z의 축 방향 경사.
|
||||
* - 저판: 하면 = 기초 하단 EL, 두께 1.1 m. 폭은 t −1.8 ~ +2.8 m (앞굽 1.2, 뒷굽 1.0, 벽체 2.4). 길이 = 교좌면 길이 L.
|
||||
* - 말뚝: 열 위치 t = −1.15 / +0.85 / +2.15 m. 열 안 본수 = int((L − 1.3)/2.0) + 1, 양끝 0.65 m 띄움, 균등 간격.
|
||||
* 말뚝 선단 EL = 기초 하단 EL − 말뚝길이. (skew 평행사변형에서 열 끝이 밀리는 보정은 아직 없다 — QA-5 이후.)
|
||||
|
||||
@@ -47,8 +47,10 @@ export interface AbutParapetGeom {
|
||||
/** 흉벽 상단 z at u = 0 and u = L (저판 하면에서), straight between. 브라켓 상면 = 흉벽 상단 − bracketH. */
|
||||
top: [number, number];
|
||||
bracketH: number;
|
||||
/** 흉벽 높이 입력 (교좌면 → 흉벽 상단). 철근표 대표 단면 = 흉벽 높이가 이 값인 곳 (기획서 §2 "구간 1 끝 2500"과 같은 단면). */
|
||||
/** 벽체 높이 H_w (받침 ① 교좌면 → 흉벽 상단, 받침 ① 위치에서 연직). */
|
||||
Hp: number;
|
||||
/** 받침 ① 위치 u (mm). 철근표 대표 단면 (사용자 도면 2026-11-04). */
|
||||
ref: number;
|
||||
/** 접속슬래브: 폭 B (교대축 방향), 다웰 선(브라켓 가운데)에서 슬래브 양 측면의 u, 슬래브 끝 t, 측별 모서리 모따기 (0 = 없음). */
|
||||
slab: { B: number; a: number; b: number; nearT: number; chamferA: number; chamferB: number } | null;
|
||||
}
|
||||
@@ -62,7 +64,7 @@ export interface AbutParapetBar {
|
||||
spacing: string;
|
||||
actual: string;
|
||||
shape: string;
|
||||
/** 대표 길이 (흉벽 높이 = H_p 단면) and the range. */
|
||||
/** 대표 길이 (받침 ① 위치 단면) and the range. */
|
||||
length: number;
|
||||
lengthMin: number;
|
||||
lengthMax: number;
|
||||
@@ -84,7 +86,7 @@ export interface AbutParapetRebar {
|
||||
x: { P1: number; P2back: number; P3: number; P4: number; P5up: number; P5low: number; P10: number };
|
||||
/** 연직 격자 오프셋 (저판 격자 기준). */
|
||||
offsets: { P1: number; P2: number; P3: number };
|
||||
/** 대표 단면 (흉벽 높이 = H_p)의 단 수, and that section's u. */
|
||||
/** 대표 단면 (받침 ① 위치)의 단 수, and that section's u. */
|
||||
levels: { P4: number; P5up: number; P5low: number; P6: number; P7: number };
|
||||
uRef: number;
|
||||
dowel: { n: number; e: number; moved: number; positions: number[] };
|
||||
@@ -110,10 +112,15 @@ export function parapetRebarIssues(s: AbutParapetRebarSettings): string[] {
|
||||
}
|
||||
|
||||
/** 흉벽 형상 from the 벽체 형상 (브라켓 상면 + 브라켓 높이 = 흉벽 상단) and the 접속슬래브 values (mm). */
|
||||
export function abutParapetGeom(wall: AbutWallGeom, shape: { parapetBackM: number; bracketHeightM: number; parapetHeightM: number }, slab: AbutParapetGeom['slab']): AbutParapetGeom {
|
||||
export function abutParapetGeom(
|
||||
wall: AbutWallGeom,
|
||||
shape: { parapetBackM: number; bracketHeightM: number; parapetHeightM: number },
|
||||
bearing1SM: number,
|
||||
slab: AbutParapetGeom['slab'],
|
||||
): AbutParapetGeom {
|
||||
const mm = (m: number) => Math.round(m * 1e6) / 1e3;
|
||||
const bracketH = mm(shape.bracketHeightM);
|
||||
return { wall, parapetBack: mm(shape.parapetBackM), top: [wall.bracketTop[0] + bracketH, wall.bracketTop[1] + bracketH], bracketH, Hp: mm(shape.parapetHeightM), slab };
|
||||
return { wall, parapetBack: mm(shape.parapetBackM), top: [wall.bracketTop[0] + bracketH, wall.bracketTop[1] + bracketH], bracketH, Hp: mm(shape.parapetHeightM), ref: mm(bearing1SM), slab };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,21 +167,8 @@ export function abutParapetRebar(g: AbutParapetGeom, wall: AbutWallRebar, change
|
||||
const seat = (u: number) => W.HF + segs[segIndex(u)].H;
|
||||
const top = (u: number) => g.top[0] + ((g.top[1] - g.top[0]) * u) / W.L;
|
||||
const br = (u: number) => top(u) - g.bracketH;
|
||||
// 대표 단면: 흉벽 높이(상단 − 교좌면)가 입력값 H_p인 곳 (기획서 §2의 "구간 1 끝 2500" 단면). Linear in each step, so the
|
||||
// closest point is a clamped root per step.
|
||||
let uRef = W.L;
|
||||
let best = Infinity;
|
||||
for (const sg of segs) {
|
||||
const hAt = (u: number) => top(u) - (W.HF + sg.H);
|
||||
const h0 = hAt(sg.from);
|
||||
const h1 = hAt(sg.to);
|
||||
const f = h1 === h0 ? 0 : Math.min(1, Math.max(0, (g.Hp - h0) / (h1 - h0)));
|
||||
const u = sg.from + f * (sg.to - sg.from);
|
||||
if (Math.abs(hAt(u) - g.Hp) < best - 1e-9) {
|
||||
best = Math.abs(hAt(u) - g.Hp);
|
||||
uRef = Math.min(u, sg.to - 1e-6);
|
||||
}
|
||||
}
|
||||
// 대표 단면 = 받침 ① 위치 (벽체 높이 H_w의 기준선, 사용자 도면 2026-11-04). 그 단면의 흉벽 높이가 H_w다.
|
||||
const uRef = Math.min(Math.max(g.ref, 0), W.L - 1e-6);
|
||||
|
||||
// 단면 위치 (§3).
|
||||
const xP4 = -c;
|
||||
@@ -198,21 +192,25 @@ export function abutParapetRebar(g: AbutParapetGeom, wall: AbutWallRebar, change
|
||||
lengths.set(mark, [...(lengths.get(mark) ?? []), { u, l: length }]);
|
||||
};
|
||||
|
||||
// P1 · P2 · P3 (§4.2).
|
||||
// P1 · P2 · P3 (§4.2). Lengths as functions of u, so the 철근표 대표 can be taken exactly at the 받침 ① 단면.
|
||||
const lenP1 = (u: number) => top(u) - c - offP2 - (seat(u) - ld.H16);
|
||||
const lenP2 = (u: number) => ld.H16 + (xP1 - xP2back) + (top(u) - c - (br(u) - ld.H16));
|
||||
const lenP3 = (u: number) => br(u) - c - (seat(u) - ld.H16) + (xP4 - xP3);
|
||||
let shortBack = false;
|
||||
base.forEach((u, i) => {
|
||||
const z0 = seat(u);
|
||||
const zt = top(u);
|
||||
const zb = br(u);
|
||||
if (i % 2 === 0) put('P1', [[u, xP1, z0 - ld.H16], [u, xP1, zt - c - offP2]], zt - c - offP2 - (z0 - ld.H16), u);
|
||||
if (i % 2 === 0) put('P1', [[u, xP1, z0 - ld.H16], [u, xP1, zt - c - offP2]], lenP1(u), u);
|
||||
const u2 = u + offP2;
|
||||
const zh = zt - c;
|
||||
const backEnd = zb - ld.H16;
|
||||
if (backEnd < seat(u2)) shortBack = true;
|
||||
put('P2', [[u2, xP1, zh - ld.H16], [u2, xP1, zh], [u2, xP2back, zh], [u2, xP2back, backEnd]], ld.H16 + (xP1 - xP2back) + (zh - backEnd), u2);
|
||||
put('P2', [[u2, xP1, zh - ld.H16], [u2, xP1, zh], [u2, xP2back, zh], [u2, xP2back, backEnd]], lenP2(u2), u2);
|
||||
const u3 = u + offP3;
|
||||
put('P3', [[u3, xP3, seat(u3) - ld.H16], [u3, xP3, br(u3) - c], [u3, xP4, br(u3) - c]], br(u3) - c - (seat(u3) - ld.H16) + (xP4 - xP3), u3);
|
||||
put('P3', [[u3, xP3, seat(u3) - ld.H16], [u3, xP3, br(u3) - c], [u3, xP4, br(u3) - c]], lenP3(u3), u3);
|
||||
});
|
||||
const atRef: Partial<Record<ParapetMark, number>> = { P1: lenP1(uRef), P2: lenP2(uRef), P3: lenP3(uRef) };
|
||||
if (shortBack) add('RB-AP01', 'W', `흉벽 높이 − 브라켓 높이가 l_d ${ld.H16}보다 작아 P2 뒤 다리가 교좌면 아래로 내려갑니다.`);
|
||||
|
||||
// 수평 단 (§5): 흉벽 상단에서 c + k·s_v 아래, 상단 경사와 나란함.
|
||||
@@ -340,7 +338,7 @@ export function abutParapetRebar(g: AbutParapetGeom, wall: AbutWallRebar, change
|
||||
if (kinds > 10) add('RB-AP04', 'I', `${b.mark} 길이가 흉벽 상단 경사 · 계단으로 ${kinds}종입니다 (철근표에는 범위).`);
|
||||
const splice = splicePlan(max, d(b.size), fy(b.size), material.fck) ?? undefined;
|
||||
if (splice) add('RB-AP07', splice.over ? 'W' : 'I', `${b.mark} ${Math.round(max)} > 정척 ${STANDARD_BAR}: ${splice.pieces}조각 이음 l_s ${splice.lap}.`);
|
||||
bars.push({ ...b, note: splice ? `${b.note} · ${splice.pieces}조각 이음 l_s ${splice.lap}` : b.note, length: Math.round(ref.l), lengthMin: Math.round(Math.min(...ls)), lengthMax: Math.round(max), count: all.length, splice });
|
||||
bars.push({ ...b, note: splice ? `${b.note} · ${splice.pieces}조각 이음 l_s ${splice.lap}` : b.note, length: Math.round(atRef[b.mark] ?? ref.l), lengthMin: Math.round(Math.min(...ls)), lengthMax: Math.round(max), count: all.length, splice });
|
||||
};
|
||||
const vMod = s.sVert !== undefined;
|
||||
const hMod = s.sHoriz !== undefined;
|
||||
|
||||
@@ -45,7 +45,8 @@ export interface AbutShape {
|
||||
|
||||
export const ABUT_SHAPE_DEFAULT: AbutShape = {
|
||||
wallFrontMm: 1600,
|
||||
parapetHeightMm: 2500,
|
||||
/** 벽체 높이 H_w: 받침 ① 위치에서 받침 ① 교좌면 ~ 흉벽 상단 연직 거리 (사용자 도면, 2026-11-04). 모든 교대 기본값. */
|
||||
parapetHeightMm: 3326,
|
||||
parapetThicknessMm: 500,
|
||||
bracketHeightMm: 850,
|
||||
bracketWidthMm: 300,
|
||||
@@ -166,7 +167,7 @@ export function shapeIssues(changes: Partial<AbutShape>): string[] {
|
||||
const out: string[] = [];
|
||||
if (!(v.wallFrontMm > 0)) out.push('교좌면 폭은 0보다 커야 합니다.');
|
||||
if (!(v.bracketWidthMm >= 0)) out.push('브라켓 폭은 0 이상이어야 합니다.');
|
||||
if (!(v.parapetHeightMm > 0)) out.push('흉벽 높이는 0보다 커야 합니다.');
|
||||
if (!(v.parapetHeightMm > 0)) out.push('벽체 높이(받침 ① 기준)는 0보다 커야 합니다.');
|
||||
if (!(v.parapetThicknessMm > 0)) out.push('흉벽 두께는 0보다 커야 합니다.');
|
||||
if (!(v.bracketHeightMm >= 0)) out.push('브라켓 높이는 0 이상이어야 합니다.');
|
||||
if (!(v.footingThicknessMm > 0)) out.push('저판 두께는 0보다 커야 합니다.');
|
||||
|
||||
@@ -30,7 +30,7 @@ export function abutSummaryRows(rec: AbutRecord): [string, string][] {
|
||||
['받침블럭 높이 (패드 포함)', mm(first.blockHeightMm)],
|
||||
['벽체 높이 (구간 ①)', m3(members.wallSegments.find((w) => Math.abs(w.seatEl - first.seatEl) < 1e-9)?.heightM ?? NaN)],
|
||||
['벽체 두께 · 전면', `${Math.round(shape.wallFrontM * 1000 - shape.wallBackM * 1000)} mm · ${Math.round(shape.wallFrontM * 1000)} mm`],
|
||||
['흉벽 높이 · 두께', `${Math.round(shape.parapetHeightM * 1000)} mm · ${Math.round(-shape.parapetBackM * 1000)} mm`],
|
||||
['벽체 높이 (받침 ① 기준) · 흉벽 두께', `${Math.round(shape.parapetHeightM * 1000)} mm · ${Math.round(-shape.parapetBackM * 1000)} mm`],
|
||||
['흉벽 상단 EL (IP1 끝 / IP3 끝)', `${members.parapetTopStartEl.toFixed(3)} / ${members.parapetTopEndEl.toFixed(3)} m`],
|
||||
['브라켓 상면 EL (구간 ①)', m3(first.seatEl + shape.parapetHeightM - shape.bracketHeightM)],
|
||||
['흉벽 경사', `${(members.parapetSlope * 100).toFixed(3)} %`],
|
||||
|
||||
+4
-4
@@ -1885,7 +1885,7 @@ function fillAbutSlot(slot: (typeof ABUT_SLOTS)[keyof typeof ABUT_SLOTS], rec: A
|
||||
shape.append(
|
||||
el(
|
||||
'p',
|
||||
'시안 메모 · 벽체는 교좌면 계단 구간마다 높이가 다릅니다. 흉벽 높이 2500은 ① 구간 교좌면 기준(가정, QA-4)이고, Level 좌·우는 계산에 쓰지 않습니다. 인접 무시 기준보다 작은 교좌면 단차는 한 구간으로 묶습니다.',
|
||||
'시안 메모 · 벽체는 교좌면 계단 구간마다 높이가 다릅니다. 벽체 높이 H_w(기본 3326)는 받침 ① 위치에서 받침 ① 교좌면 ~ 흉벽 상단 연직 거리이고(사용자 도면 2026-11-04, QA-4 확정), 흉벽 상단은 받침 z 경사를 따릅니다. Level 좌·우는 계산에 쓰지 않습니다. 인접 무시 기준보다 작은 교좌면 단차는 한 구간으로 묶습니다.',
|
||||
'muted small abut-memo',
|
||||
),
|
||||
);
|
||||
@@ -3144,7 +3144,7 @@ function abutParapetBuilt(rec: AbutRecord, wall: { geom: AbutWallGeom; result: A
|
||||
const chamfer = (sEnd: number) => (wings.find((w) => Math.abs(w.sEnd - sEnd) < 1e-9)?.haunchM ?? 0) * 1000;
|
||||
const B = model.wing?.slabWidthMm;
|
||||
const slabGeom = span && B !== undefined ? { B, a: span.a, b: span.b, nearT: members.shape.parapetBackM * 1000 - slab.parapetGapMm, chamferA: chamfer(0), chamferB: chamfer(model.length) } : null;
|
||||
const geom = abutParapetGeom(wall.geom, members.shape, slabGeom);
|
||||
const geom = abutParapetGeom(wall.geom, members.shape, model.bearings[0].s, slabGeom);
|
||||
return { geom, result: abutParapetRebar(geom, wall.result, rec.rebar?.parapet, materialFor(state.material, state.fckByMember, 'abutBody'), rec.name) };
|
||||
} catch {
|
||||
return null;
|
||||
@@ -3527,11 +3527,11 @@ function fillAbutParapetRebarCard(box: HTMLDivElement, rec: AbutRecord, built: {
|
||||
svg: (pick) => (view === 'sec' ? abutParapetSectionSvg(geom, r, pick) : view === 'elev' ? abutParapetElevationSvg(geom, r, pick) : abutParapetDowelSvg(geom, r, pick)),
|
||||
caption:
|
||||
view === 'sec'
|
||||
? `교대축 직각 단면 · 대표 단면 (흉벽 높이 ${lenText(geom.Hp)}) · 왼쪽 브라켓 배면 → 오른쪽 흉벽 전면`
|
||||
? `교대축 직각 단면 · 받침 ① 위치 (벽체 높이 H_w ${lenText(geom.Hp)}) · 왼쪽 브라켓 배면 → 오른쪽 흉벽 전면`
|
||||
: view === 'elev'
|
||||
? '교대축 단면 · 전면 · 왼쪽 IP3 → 오른쪽 IP1 · 수평 P4는 흉벽 상단 경사와 나란함, 끝면 P9 (위) · P8 (아래) · P11'
|
||||
: `P10 다웰 배치 · 평면 (접속슬래브 폭 ${geom.slab ? Math.round(geom.slab.B) : '—'}) · 다웰이 P3 수평 다리와 겹치면 가장 가까운 자리로 옮김 (Q-P5)`,
|
||||
memo: `흉벽 상단선은 받침 ① · 마지막 받침으로 정해져 흉벽 높이가 위치마다 다릅니다 (IP1 ${Math.round(geom.top[0] - (geom.wall.HF + geom.wall.segments[0].H))} · IP3 ${Math.round(geom.top[1] - (geom.wall.HF + geom.wall.segments[geom.wall.segments.length - 1].H))}). 철근표 대표 길이 · 단 수는 흉벽 높이 ${lenText(geom.Hp)} 단면, 괄호는 범위입니다.`,
|
||||
memo: `흉벽 상단은 받침 ① 교좌면 + 벽체 높이 H_w ${lenText(geom.Hp)}에서 받침 z 경사를 따라 기울어 교좌면 위 높이가 위치마다 다릅니다 (IP1 ${Math.round(geom.top[0] - (geom.wall.HF + geom.wall.segments[0].H))} · IP3 ${Math.round(geom.top[1] - (geom.wall.HF + geom.wall.segments[geom.wall.segments.length - 1].H))}). 철근표 대표 길이 · 단 수는 받침 ① 위치 단면, 괄호는 범위입니다.`,
|
||||
legendHtml: `<span style="color:${C.vertical}">━ 연직 (P1–P3 · P11)</span> · <span style="color:${C.horizontal}">━ 수평 (P4 · P5)</span> · <span style="color:${C.tie}">┅ 간격재 (P6 · P7)</span> · <span style="color:${C.side}">┅ 측면 (P8 · P9) · 다웰 P10</span>`,
|
||||
baseTitle: '기본간격 · 흉벽 (부재별 입력) · 교대축 직각은 두지 않음',
|
||||
base: [
|
||||
|
||||
@@ -24,7 +24,7 @@ const seatAt = (g: AbutParapetGeom, u: number) => {
|
||||
const nearest = (paths: ParapetBarPath[], mark: ParapetMark, u: number, at = 1) =>
|
||||
paths.filter((p) => p.mark === mark).reduce<ParapetBarPath | undefined>((a, b) => (!a || Math.abs(b.pts[at][0] - u) < Math.abs(a.pts[at][0] - u) ? b : a), undefined);
|
||||
|
||||
/** 교대축 직각 단면 at the 대표 단면 (흉벽 높이 = H_p): t 가로 (왼쪽 브라켓 배면 → 오른쪽 흉벽 전면), z 세로. */
|
||||
/** 교대축 직각 단면 at the 대표 단면 (받침 ① 위치): t 가로 (왼쪽 브라켓 배면 → 오른쪽 흉벽 전면), z 세로. */
|
||||
export function abutParapetSectionSvg(g: AbutParapetGeom, r: AbutParapetRebar, pick: AbutRebarPick = NO_PICK): string {
|
||||
const u = r.uRef;
|
||||
const seat = seatAt(g, u);
|
||||
|
||||
@@ -90,7 +90,7 @@ export function abutWallSectionSvg(d: AbutWallDrawing): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* 정면·배면도: 교좌면 계단(구간별 교좌면 EL), 흉벽 상단 경사, 받침 블럭, 수축줄눈, 저판. 왼쪽 IP3(① 쪽), 오른쪽 IP1.
|
||||
* 정면·배면도: 교좌면 계단(구간별 교좌면 EL), 흉벽 상단 경사, 받침 블럭, 수축줄눈, 저판, 받침 ① 기준선과 벽체 높이 H_w. 왼쪽 IP3(① 쪽), 오른쪽 IP1.
|
||||
*/
|
||||
export function abutWallElevationSvg(d: AbutWallDrawing): string {
|
||||
const k = 46;
|
||||
@@ -118,6 +118,16 @@ export function abutWallElevationSvg(d: AbutWallDrawing): string {
|
||||
parts.push(`<line x1="${X(s).toFixed(1)}" y1="${Y(parapetTopAt(d, s)).toFixed(1)}" x2="${X(s).toFixed(1)}" y2="${Y(d.footingTopEl).toFixed(1)}" stroke="${INK}" stroke-dasharray="5 3"/>`);
|
||||
parts.push(label(X(s) + 4, Y(d.footingTopEl) - 8, `수축줄눈 ${j}`, 'start'));
|
||||
}
|
||||
// 벽체 높이 H_w (사용자 도면 2026-11-04): 받침 ① 연직 기준선, 받침 ① 교좌면 ~ 흉벽 상단.
|
||||
const b1 = d.bearings.find((b) => b.no === 1) ?? d.bearings[0];
|
||||
if (b1) {
|
||||
const topB1 = parapetTopAt(d, b1.s);
|
||||
const xb = X(b1.s);
|
||||
parts.push(`<line x1="${xb.toFixed(1)}" y1="${(Y(topB1) - 14).toFixed(1)}" x2="${xb.toFixed(1)}" y2="${(Y(d.footingTopEl) + 6).toFixed(1)}" stroke="#c0392b" stroke-dasharray="10 3 2 3"/>`);
|
||||
const xd = xb - 22;
|
||||
parts.push(`<line x1="${xd.toFixed(1)}" y1="${Y(topB1).toFixed(1)}" x2="${xd.toFixed(1)}" y2="${Y(b1.seatEl).toFixed(1)}" stroke="#c0392b" marker-start="url(#hw-a)" marker-end="url(#hw-a)"/>`);
|
||||
parts.push(`<text x="${(xd - 4).toFixed(1)}" y="${((Y(topB1) + Y(b1.seatEl)) / 2).toFixed(1)}" font-size="11" fill="#c0392b" text-anchor="end">H_w ${Math.round((topB1 - b1.seatEl) * 1000)}</text>`);
|
||||
}
|
||||
const first = d.segments.find((g) => d.lengthM >= g.sFrom && d.lengthM <= g.sTo) ?? d.segments[d.segments.length - 1];
|
||||
const last = d.segments.find((g) => g.sFrom <= 0 && g.sTo >= 0) ?? d.segments[0];
|
||||
parts.push(label(X(d.lengthM), Y(d.parapetTopEndEl) - 6, el(d.parapetTopEndEl), 'start'));
|
||||
@@ -130,6 +140,7 @@ export function abutWallElevationSvg(d: AbutWallDrawing): string {
|
||||
parts.push(label((X(0) + X(d.lengthM)) / 2, H - 8, `${d.lengthM.toFixed(3)} m`));
|
||||
return [
|
||||
`<svg viewBox="0 0 ${W.toFixed(0)} ${H.toFixed(0)}" width="100%" role="img" aria-label="정면·배면도: 흉벽 상단 ${t3(d.parapetTopEndEl)} (IP3) → ${t3(d.parapetTopStartEl)} (IP1), 교좌면 계단 ${d.segments.length}구간, 수축줄눈 ${d.shrinkJointCount}">`,
|
||||
'<defs><marker id="hw-a" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#c0392b"/></marker></defs>',
|
||||
...parts,
|
||||
'</svg>',
|
||||
].join('');
|
||||
|
||||
Reference in new issue
Block a user