feat(view): round bar bends at the centre-line radius; bend factors as inputs on ③

Bars drawn through several points are one tube each, with every bend rounded by R·tan(φ/2) at the centre-line radius
(inner factor × d + d/2). The inside-radius factors (D16 and below, D19–D25, D29–D35, D38 and above) are inputs in 공통
설정, with KDS-based defaults 2 / 3 / 4 / 5. The size-class limits sit in one place (SIZE_CLASS), which also fixes
D25 (25.4) being read as the D29–D35 class in hookLength. The helper bendDeduction gives the centre-line deduction for
the length option, which is not yet applied to the 철근표 lengths.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-23 10:13:16 +09:00
1 parent 03bb1f9929
commit 30e5e75c22
7 files changed
+146 -21

No files matched your search

+48 -6
View File
@@ -8,15 +8,50 @@ export const GRADE_CHOICES = [300, 400, 500] as const;
/** Bar family by 규격 prefix: D · H · S. */
export type GradeKey = 'D' | 'H' | 'S';
/** Material inputs of ③ 공통 설정 (spec §6.2). fck and the grade per prefix are inputs. */
/**
* Inside bend radius as a multiple of the nominal diameter (입력, 2026-10-23). Defaults follow KDS 14 20 52 as
* recorded in the plan; confirm against the code. small16 is for D16 and below (ties and stirrups).
*/
export interface BendFactors {
small16: number;
main25: number;
main35: number;
main51: number;
}
export const DEFAULT_BEND: BendFactors = { small16: 2, main25: 3, main35: 4, main51: 5 };
/**
* Size classes by nominal diameter (mm). The limits lie between the sizes: D16 15.9, D25 25.4, D29 28.6, D35 34.9,
* so 26 and 36 separate D25 from D29 and D35 from D38.
*/
export const SIZE_CLASS = { small: 17, d25: 26, d35: 36 } as const;
/** Centre-line bend radius (mm) of a bar of nominal diameter db: inner radius (factor × db) + db/2. */
export function bendRadius(db: number, bend: BendFactors): number {
const factor = db <= SIZE_CLASS.small ? bend.small16 : db <= SIZE_CLASS.d25 ? bend.main25 : db <= SIZE_CLASS.d35 ? bend.main35 : bend.main51;
return factor * db + db / 2;
}
/**
* Length the centre line loses at a bend of deflection phi (rad) compared with the sharp corner of the same bar:
* 2R·tan(φ/2) − Rφ. For a 90° bend this is (2 − π/2)·R ≈ 0.429 R. Used by the centre-line length option.
*/
export function bendDeduction(phi: number, R: number): number {
return 2 * R * Math.tan(phi / 2) - R * phi;
}
/** Material inputs of ③ 공통 설정 (spec §6.2). fck, the grade per prefix and the bend factors are inputs. */
export interface Material {
/** 콘크리트 강도 fck (MPa). */
fck: number;
/** 규격 접두어별 철근 강도 (MPa): D = SD300, H = SD400, S = SD500 (입력). */
grade: Record<GradeKey, number>;
/** 굽힘 내면반경 배수 (입력). */
bend: BendFactors;
}
export const DEFAULT_MATERIAL: Material = { fck: 27, grade: { D: 300, H: 400, S: 500 } };
export const DEFAULT_MATERIAL: Material = { fck: 27, grade: { D: 300, H: 400, S: 500 }, bend: DEFAULT_BEND };
/** Yield strength (MPa) of a bar from its 규격, e.g. H19 → the H grade. */
export function fyOf(m: Material, size: string): number {
@@ -28,14 +63,21 @@ export function fyOf(m: Material, size: string): number {
* A value outside GRADE_CHOICES falls back to the default for that prefix.
*/
export function materialFrom(raw: unknown): Material {
const r = (raw ?? {}) as { fck?: unknown; grade?: Partial<Record<GradeKey, unknown>>; fyH?: unknown; fyS?: unknown };
const r = (raw ?? {}) as { fck?: unknown; grade?: Partial<Record<GradeKey, unknown>>; fyH?: unknown; fyS?: unknown; bend?: Partial<Record<keyof BendFactors, unknown>> };
const factor = (v: unknown, fallback: number) => (typeof v === 'number' && v > 0 ? v : fallback);
const bend: BendFactors = {
small16: factor(r.bend?.small16, DEFAULT_BEND.small16),
main25: factor(r.bend?.main25, DEFAULT_BEND.main25),
main35: factor(r.bend?.main35, DEFAULT_BEND.main35),
main51: factor(r.bend?.main51, DEFAULT_BEND.main51),
};
const pick = (v: unknown, fallback: number) => ((GRADE_CHOICES as readonly number[]).includes(v as number) ? (v as number) : fallback);
const grade = {
D: pick(r.grade?.D, DEFAULT_MATERIAL.grade.D),
H: pick(r.grade?.H ?? r.fyH, DEFAULT_MATERIAL.grade.H),
S: pick(r.grade?.S ?? r.fyS, DEFAULT_MATERIAL.grade.S),
};
return { fck: typeof r.fck === 'number' ? r.fck : DEFAULT_MATERIAL.fck, grade };
return { fck: typeof r.fck === 'number' ? r.fck : DEFAULT_MATERIAL.fck, grade, bend };
}
/** Members that can take their own fck on ② (부재 카드). Other members always use the 공통 fck. */
@@ -106,8 +148,8 @@ export type HookKind = 'main90' | 'tie90' | 'stirrup135';
*/
export function hookLength(kind: HookKind, db: number): number {
if (kind === 'main90') {
if (db <= 25) return 15.5 * db;
if (db <= 35) return 16.5 * db;
if (db <= SIZE_CLASS.d25) return 15.5 * db;
if (db <= SIZE_CLASS.d35) return 16.5 * db;
throw new Error(`주철근 90° 갈고리 표에 없는 지름입니다 (D${db}).`);
}
if (kind === 'tie90') {