feat(abut): ② 날개벽 카드를 시안대로 작성 (좌우 같게 · 측별 치수 · 삽도 3개)
- 선택 줄 (타일): 좌우 같게(기본) / 따로 정하기, 편집할 측 (좌 · ① 쪽 / 우 · ⑤ 쪽), 형식 일반형(기본) / 단축형 / 없음 - 측별 치수 입력 (WingDims, 교대 기록 wingDims): 길이 6500, 두께 450, 끝 높이 1500(일반형), 헌치 300. 단축형 길이 기본 = 뒷굽 끝(1.807 m). 좌우 같게이면 양쪽에 함께 적용, 켤 때 좌측 값을 우측에 복사 - 연동: 두께 → 날개벽 3D · 접속슬래브 폭(geometry slabWidthMm) · 슬래브 평면, 헌치 → 연직 헌치 · 저판 상면 헌치 (0이면 없음) - 자동 값: 경사 (A1 = −i), 등단면 · 변화단면 경계(뒷굽 끝), 끝 EL 상단 / 하단 (가정), 단축형 하단 = 확대기초 상면 (뒷굽 밖은 AI02 안내) - 삽도 3개 (src/view/abut-wing-svg.ts): 평면(좌우 날개벽 · 접속슬래브, abutPlanOutline 공유), 측면(수평 단 · 뒷굽 끝), 날개벽–흉벽 모서리 모식도(헌치 · 브라켓 · 슬래브 모따기 · 간격) - "생성되는 날개벽" 줄과 시안 메모. 프로젝트 파일 검사에 wingDims · wingsLinked 추가 - 테스트 7건 추가 · 갱신. 전체 669건 통과, 타입 검사 · 빌드 통과. 브라우저 시각 확인은 미실시 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a025a61209
commit
80e5302192
12 files changed
+610
-53
No files matched your search
+65
-17
@@ -21,11 +21,43 @@ export const WING_THICKNESS_M = 0.45;
|
||||
/** 단부 높이 (끝점 상단에서 단부 하단까지, m). */
|
||||
export const WING_END_HEIGHT_M = 1.5;
|
||||
|
||||
/** 날개벽 치수 입력 (측별, mm, 2026-11-04 시안 ② 날개벽 카드). 단축형 길이의 기본값은 뒷굽 끝까지의 거리다. */
|
||||
export interface WingDims {
|
||||
lengthMm: number;
|
||||
thicknessMm: number;
|
||||
endHeightMm: number;
|
||||
haunchMm: number;
|
||||
}
|
||||
|
||||
export const WING_DIMS_DEFAULT: WingDims = { lengthMm: 6500, thicknessMm: 450, endHeightMm: 1500, haunchMm: 300 };
|
||||
|
||||
/** The dims of one side: the user's values over the defaults; 단축형 without a length input runs to the 뒷굽 끝. */
|
||||
export function resolveWingDims(form: WingForm, changes: Partial<WingDims> | undefined, heelDistanceM: number): WingDims {
|
||||
const base = { ...WING_DIMS_DEFAULT, ...(changes ?? {}) };
|
||||
if (form === 'short' && changes?.lengthMm === undefined) base.lengthMm = Math.round(heelDistanceM * 1000);
|
||||
return base;
|
||||
}
|
||||
|
||||
/** Input problems of one side's dims, in Korean; empty when the wing can be built. */
|
||||
export function wingDimsIssues(d: WingDims): string[] {
|
||||
const out: string[] = [];
|
||||
if (!(d.lengthMm > 0)) out.push('날개벽 길이는 0보다 커야 합니다.');
|
||||
if (!(d.thicknessMm > 0)) out.push('날개벽 두께는 0보다 커야 합니다.');
|
||||
if (!(d.endHeightMm > 0)) out.push('날개벽 끝 높이는 0보다 커야 합니다.');
|
||||
if (!(d.haunchMm >= 0)) out.push('헌치는 0 이상이어야 합니다.');
|
||||
return out;
|
||||
}
|
||||
|
||||
export interface WingSolid {
|
||||
side: 'left' | 'right';
|
||||
form: Exclude<WingForm, 'none'>;
|
||||
/** 날개벽이 시작하는 축 끝: 0 (IP1 끝) 또는 L (IP3 끝). */
|
||||
sEnd: number;
|
||||
/** 이 측의 치수 (m): 길이, 두께, 끝 높이, 헌치 다리. */
|
||||
lengthM: number;
|
||||
thicknessM: number;
|
||||
endHeightM: number;
|
||||
haunchM: number;
|
||||
/** 평면 네 꼭짓점 (E, N), 순서: 시작, 끝, 끝 안쪽, 시작 안쪽. */
|
||||
corners: [AbutPoint, AbutPoint, AbutPoint, AbutPoint];
|
||||
topStartEl: number;
|
||||
@@ -66,7 +98,12 @@ export interface Haunch {
|
||||
|
||||
export const HAUNCH_LEG_M = 0.3;
|
||||
|
||||
export function buildWings(model: AbutModel, members: AbutMembers, forms?: { left: WingForm; right: WingForm }): WingSolid[] {
|
||||
export function buildWings(
|
||||
model: AbutModel,
|
||||
members: AbutMembers,
|
||||
forms?: { left: WingForm; right: WingForm },
|
||||
dims?: { left?: Partial<WingDims>; right?: Partial<WingDims> },
|
||||
): WingSolid[] {
|
||||
if (!model.wing) return [];
|
||||
const L = model.length;
|
||||
const wings = forms ?? model.wing.wings;
|
||||
@@ -89,26 +126,30 @@ export function buildWings(model: AbutModel, members: AbutMembers, forms?: { lef
|
||||
|
||||
const build = (side: 'left' | 'right', sEnd: number, topStart: number, form: Exclude<WingForm, 'none'>): WingSolid => {
|
||||
const a = sEnd === 0 ? 1 : -1;
|
||||
const dm = resolveWingDims(form, dims?.[side], heelDistanceM);
|
||||
const length = dm.lengthMm / 1000;
|
||||
const thick = dm.thicknessMm / 1000;
|
||||
const leg = dm.haunchMm / 1000;
|
||||
const p0 = at(sEnd);
|
||||
const p1 = { e: p0.e + w.e * WING_LENGTH_M, n: p0.n + w.n * WING_LENGTH_M };
|
||||
const inward = { e: model.u.e * a * WING_THICKNESS_M, n: model.u.n * a * WING_THICKNESS_M };
|
||||
const topEnd = topStart + slope * WING_LENGTH_M;
|
||||
const bottomEnd = form === 'general' ? topEnd - WING_END_HEIGHT_M : footingTop;
|
||||
const p1 = { e: p0.e + w.e * length, n: p0.n + w.n * length };
|
||||
const inward = { e: model.u.e * a * thick, n: model.u.n * a * thick };
|
||||
const topEnd = topStart + slope * length;
|
||||
const bottomEnd = form === 'general' ? topEnd - dm.endHeightMm / 1000 : footingTop;
|
||||
const topAt = (d: number) => topStart + slope * d;
|
||||
const topLine = [
|
||||
{ dM: 0, el: topStart },
|
||||
{ dM: WING_LENGTH_M, el: topEnd },
|
||||
{ dM: length, el: topEnd },
|
||||
];
|
||||
const bottomLine =
|
||||
form === 'general' && heelDistanceM < WING_LENGTH_M
|
||||
form === 'general' && heelDistanceM < length
|
||||
? [
|
||||
{ dM: 0, el: footingTop },
|
||||
{ dM: heelDistanceM, el: footingTop },
|
||||
{ dM: WING_LENGTH_M, el: bottomEnd },
|
||||
{ dM: length, el: bottomEnd },
|
||||
]
|
||||
: [
|
||||
{ dM: 0, el: footingTop },
|
||||
{ dM: WING_LENGTH_M, el: form === 'general' ? bottomEnd : footingTop },
|
||||
{ dM: length, el: form === 'general' ? bottomEnd : footingTop },
|
||||
];
|
||||
|
||||
// 헌치: 위 헌치는 흉벽 배면(−0.5), 아래 헌치는 벽체 배면(−0.8)의 모서리에 둔다.
|
||||
@@ -122,8 +163,8 @@ export function buildWings(model: AbutModel, members: AbutMembers, forms?: { lef
|
||||
const d = Math.abs(t) / k;
|
||||
// The corner lies on the 날개벽 내측면 (the face between the two wings), WING_THICKNESS_M in from the axis end.
|
||||
const inwardUnit = { e: model.u.e * a, n: model.u.n * a };
|
||||
const c = shift(pointAt(d), inwardUnit, WING_THICKNESS_M);
|
||||
const corners: [AbutPoint, AbutPoint, AbutPoint] = [c, shift(c, w, HAUNCH_LEG_M), shift(c, inwardUnit, HAUNCH_LEG_M)];
|
||||
const c = shift(pointAt(d), inwardUnit, thick);
|
||||
const corners: [AbutPoint, AbutPoint, AbutPoint] = [c, shift(c, w, leg), shift(c, inwardUnit, leg)];
|
||||
return {
|
||||
kind,
|
||||
corners,
|
||||
@@ -132,14 +173,17 @@ export function buildWings(model: AbutModel, members: AbutMembers, forms?: { lef
|
||||
};
|
||||
};
|
||||
// 연직 헌치 (2026-11-03, 내측면 모서리): upper at the 흉벽 배면, lower at the 벽체 배면.
|
||||
const haunches = [haunchAt('upper', members.shape.parapetBackM), haunchAt('lower', members.shape.wallBackM)];
|
||||
const haunches = leg > 0 ? [haunchAt('upper', members.shape.parapetBackM), haunchAt('lower', members.shape.wallBackM)] : [];
|
||||
|
||||
// 저판 상면 헌치 (수평): from the 벽체 배면 to the 뒷굽, on the 저판 상면 next to the 날개벽 내측면.
|
||||
const footingHaunch = {
|
||||
dMFrom: Math.abs(members.shape.wallBackM) / k,
|
||||
dMTo: heelDistanceM,
|
||||
el: footingTop,
|
||||
};
|
||||
const footingHaunch =
|
||||
leg > 0
|
||||
? {
|
||||
dMFrom: Math.abs(members.shape.wallBackM) / k,
|
||||
dMTo: Math.min(heelDistanceM, length),
|
||||
el: footingTop,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
// 수평 단 (QA-12): 등단면 = 끝 상단 − 1.5 m 위, 변화단면1 = 그 아래 ~ 교좌면 EL, 변화단면2 = 교좌면 EL ~ 저판 상면.
|
||||
// 교좌면 EL은 날개벽 끝에 가장 가까운 받침의 교좌면이다.
|
||||
@@ -157,6 +201,10 @@ export function buildWings(model: AbutModel, members: AbutMembers, forms?: { lef
|
||||
side,
|
||||
form,
|
||||
sEnd,
|
||||
lengthM: length,
|
||||
thicknessM: thick,
|
||||
endHeightM: dm.endHeightMm / 1000,
|
||||
haunchM: leg,
|
||||
corners: [p0, p1, { e: p1.e + inward.e, n: p1.n + inward.n }, { e: p0.e + inward.e, n: p0.n + inward.n }],
|
||||
topStartEl: topStart,
|
||||
topEndEl: topEnd,
|
||||
|
||||
Reference in new issue
Block a user