The reader takes TEXT and MTEXT entities (x, y, text). The EL.X header row names the columns; the No column holds the bearing numbers; each value is the nearest entry under its header on the same row. On the 운곡교 교각도(2) drawing all ten bearings read back the reference values (EL.X, h1, h2). The 도면 불러오기 button accepts .dxf as well as sheets. The test fixture holds only the table's text entries. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
95 lines
4.1 KiB
TypeScript
95 lines
4.1 KiB
TypeScript
/**
|
|
* 도면에서 받침 ELEVATION TABLE 읽기 (승인 2026-10-23). Reads the TEXT entities of a DXF file and places each value
|
|
* by position: the header row names the columns (EL.X, h1 … h4, A, B, C1 …), the first column holds the bearing No.
|
|
* Pure: works on the text entities (x, y, text) of the drawing, not on the file itself.
|
|
*/
|
|
import type { DrawingBearing } from './bearing-ref';
|
|
|
|
export interface DxfText {
|
|
x: number;
|
|
y: number;
|
|
text: string;
|
|
}
|
|
|
|
/** Reads TEXT and MTEXT entities (x, y, string) from DXF text. */
|
|
export function readDxfTexts(dxf: string): DxfText[] {
|
|
const lines = dxf.split(/\r?\n/);
|
|
const pairs: [string, string][] = [];
|
|
for (let i = 0; i + 1 < lines.length; i += 2) pairs.push([lines[i].trim(), lines[i + 1].trim()]);
|
|
const out: DxfText[] = [];
|
|
for (let i = 0; i < pairs.length; i++) {
|
|
if (pairs[i][0] !== '0' || (pairs[i][1] !== 'TEXT' && pairs[i][1] !== 'MTEXT')) continue;
|
|
let text: string | null = null;
|
|
let x = NaN;
|
|
let y = NaN;
|
|
let j = i + 1;
|
|
for (; j < pairs.length && pairs[j][0] !== '0'; j++) {
|
|
const [c, v] = pairs[j];
|
|
if (c === '1') text = v;
|
|
if (c === '10') x = Number(v);
|
|
if (c === '20') y = Number(v);
|
|
}
|
|
if (text !== null && Number.isFinite(x) && Number.isFinite(y)) out.push({ x, y, text });
|
|
i = j - 1;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
const norm = (s: string) => s.replace(/[\s.'"]/g, '').toLowerCase();
|
|
const NUMBER = /^-?\d+(\.\d+)?$/;
|
|
|
|
export interface BearingTableRow {
|
|
no: number;
|
|
values: Record<string, number>;
|
|
}
|
|
|
|
export interface BearingTableResult {
|
|
/** Values per column as read from the drawing (keyed by header name). */
|
|
rows: BearingTableRow[];
|
|
/** The rows in the form the 받침 입력 uses (EL.X in m, h1 and h2 in mm). */
|
|
drawing: DrawingBearing[];
|
|
errors: string[];
|
|
}
|
|
|
|
/**
|
|
* Reads the bearing ELEVATION TABLE. The header is the text row with "EL.X" (as EL.'X', EL. "X"); the No column is the
|
|
* integer column just left of the 교량받침 column; each value sits under its header (x within 150) on the same row (y within 120).
|
|
*/
|
|
export function parseBearingElevationTable(texts: DxfText[]): BearingTableResult {
|
|
const errors: string[] = [];
|
|
const elHeader = texts.find((t) => norm(t.text) === 'el' + 'x' || norm(t.text) === 'elx');
|
|
if (!elHeader) return { rows: [], drawing: [], errors: ['EL.X 머리글을 찾지 못했습니다.'] };
|
|
const headerY = elHeader.y;
|
|
// Column headers: texts on the header row (within 60 of its y) with a short name (EL.X, h1 … h4, A, B, C1 …).
|
|
const headerRow = texts.filter((t) => Math.abs(t.y - headerY) <= 60 && /^(elx|h\d|[a-z]\d?)$/.test(norm(t.text)));
|
|
const cols = headerRow.map((t) => ({ key: norm(t.text), x: t.x }));
|
|
const noHeader = texts.find((t) => norm(t.text) === 'no');
|
|
// The No column: integers 1…10 below the header, within 150 of the No header (or the left-most integer column).
|
|
const numbers = texts.filter((t) => NUMBER.test(t.text) && Number.isInteger(Number(t.text)) && Number(t.text) >= 1 && Number(t.text) <= 99 && t.y < headerY - 60);
|
|
if (numbers.length === 0) return { rows: [], drawing: [], errors: ['받침 번호 열을 찾지 못했습니다.'] };
|
|
const noX = noHeader ? noHeader.x : Math.min(...numbers.map((t) => t.x));
|
|
const noCells = numbers.filter((t) => Math.abs(t.x - noX) <= 250).sort((a, b) => b.y - a.y);
|
|
|
|
const rows: BearingTableRow[] = [];
|
|
for (const cell of noCells) {
|
|
const values: Record<string, number> = {};
|
|
for (const col of cols) {
|
|
const hit = texts.find((t) => Math.abs(t.x - col.x) <= 150 && Math.abs(t.y - cell.y) <= 120 && NUMBER.test(t.text));
|
|
if (hit) values[col.key] = Number(hit.text);
|
|
}
|
|
rows.push({ no: Number(cell.text), values });
|
|
}
|
|
const drawing: DrawingBearing[] = [];
|
|
for (const r of rows) {
|
|
const elx = r.values.elx;
|
|
const h1 = r.values.h1;
|
|
const h2 = r.values.h2;
|
|
if (elx === undefined || h1 === undefined || h2 === undefined) {
|
|
errors.push(`No ${r.no}: EL.X·h1·h2 중 값이 없습니다.`);
|
|
continue;
|
|
}
|
|
drawing.push({ no: r.no, elx, h1, h2 });
|
|
}
|
|
return { rows, drawing: drawing.sort((a, b) => a.no - b.no), errors };
|
|
}
|