fix(view): find the table's header and No column by their place on the table row, not by the first label in the file
The drawing has other "EL.X" and "NO" labels. The header is now the EL.X label with the most table column names to its right on the same row; the No header is the NO label on that row. The full 운곡교 교각도(2) DXF reads all ten bearings (EL.X, h1, h2) without errors. Tests add the distractor labels. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4048c326e3
commit
55abc4be13
2 files changed
+24
-3
No files matched your search
+11
-3
@@ -57,13 +57,21 @@ export interface BearingTableResult {
|
||||
*/
|
||||
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 머리글을 찾지 못했습니다.'] };
|
||||
// The drawing has other "EL.X" labels (leader lines). The table header is the one with h1 and h2 on the same row.
|
||||
// The header is the EL.X label with the most table column names on its row, to its right (within 2500).
|
||||
const TABLE_NAMES = ['h1', 'h2', 'h3', 'h4', 'a', 'b', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 's1', 's2'];
|
||||
const rowScore = (t: DxfText) =>
|
||||
texts.filter((u) => Math.abs(u.y - t.y) <= 60 && u.x > t.x && u.x - t.x <= 2500 && TABLE_NAMES.includes(norm(u.text))).length;
|
||||
const candidates = texts.filter((t) => norm(t.text) === 'elx');
|
||||
const best = candidates.map((t) => ({ t, score: rowScore(t) })).sort((a, b) => b.score - a.score)[0];
|
||||
if (!best || best.score < 2) return { rows: [], drawing: [], errors: ['EL.X 머리글(표의 열 이름과 같은 행)을 찾지 못했습니다.'] };
|
||||
const elHeader = best.t;
|
||||
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 header is the 'NO' label on the table's header row (the drawing has other 'NO' labels).
|
||||
const noHeader = texts.find((t) => norm(t.text) === 'no' && Math.abs(t.y - headerY) <= 200);
|
||||
// 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: ['받침 번호 열을 찾지 못했습니다.'] };
|
||||
|
||||
@@ -22,6 +22,19 @@ describe('도면 받침 ELEVATION TABLE 읽기 (DXF 텍스트, 승인 2026-10-23
|
||||
}
|
||||
});
|
||||
|
||||
it('a leader-line "EL. X" label placed before the table does not replace the table header', () => {
|
||||
const distractor = { x: 11102.8, y: 11521.7, text: 'EL. "X"' };
|
||||
const r = parseBearingElevationTable([distractor, ...texts]);
|
||||
expect(r.errors).toEqual([]);
|
||||
expect(r.drawing.map((d) => d.no)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
});
|
||||
|
||||
it('a stray "NO" label elsewhere on the drawing does not move the No column', () => {
|
||||
const r = parseBearingElevationTable([{ x: 100, y: 100, text: 'NO.' }, ...texts]);
|
||||
expect(r.errors).toEqual([]);
|
||||
expect(r.drawing.map((d) => d.no)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
});
|
||||
|
||||
it('reads TEXT entities from DXF text (x, y, string)', () => {
|
||||
const dxf = ['0', 'SECTION', '2', 'ENTITIES', '0', 'TEXT', '10', '100.5', '20', '200.25', '1', 'EL. "X"', '0', 'ENDSEC'].join('\n');
|
||||
expect(readDxfTexts(dxf)).toEqual([{ x: 100.5, y: 200.25, text: 'EL. "X"' }]);
|
||||
|
||||
Reference in new issue
Block a user