feat: DWG 내장 복합 라인타입 동적 바인딩 및 텍스트 갭 정렬 보정

This commit is contained in:
minsung
2026-07-31 09:24:48 +09:00
parent 3d3e052f3e
commit 14d1f06927
3 changed files with 131 additions and 18 deletions
+9 -3
View File
@@ -30,12 +30,18 @@ async function runE2EDataTest() {
} }
} }
console.log('\n4. Verifying Complex Linetype Math in Viewer2D:'); console.log('\n4. Verifying Complex Linetype Math in Viewer2D with DWG Embedded Data:');
const { getLinPattern } = require('./src/viewer2d/linParser.ts'); const { getLinPattern, mergeDwgLinetypePattern } = await import('file:///d:/MYCLAUDE_PROJECT/dwg-dxf-viewer-sample/src/viewer2d/linParser.ts');
const targetTypes = ['H-DICHL06', 'H-DICHL06R', 'H-DICHL02R', 'H-DICHL01R', 'H-DICHL05']; const targetTypes = ['H-DICHL06', 'H-DICHL06R', 'H-DICHL02R', 'H-DICHL01R', 'H-DICHL05'];
const dwgLtMap = new Map();
for (const lt of lineTypes) {
if (lt.name && lt.pattern) dwgLtMap.set(lt.name.toUpperCase(), lt);
}
for (const name of targetTypes) { for (const name of targetTypes) {
const pattern = getLinPattern(name); const dwgLt = dwgLtMap.get(name);
const pattern = mergeDwgLinetypePattern(name, dwgLt?.pattern || [], dwgLt?.description);
if (!pattern) { if (!pattern) {
console.error(` [FAIL] Pattern ${name} not found in registry!`); console.error(` [FAIL] Pattern ${name} not found in registry!`);
continue; continue;
+33 -15
View File
@@ -11,7 +11,7 @@ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { aciToHex } from './aciColors.js'; import { aciToHex } from './aciColors.js';
import { buildAllowedOwnerHexes, listCadSpaces } from './cadSpaces'; import { buildAllowedOwnerHexes, listCadSpaces } from './cadSpaces';
import { SlugTextEngine, SlugTextBatch } from './slugText'; import { SlugTextEngine, SlugTextBatch } from './slugText';
import { getLinPattern } from './linParser'; import { getLinPattern, mergeDwgLinetypePattern } from './linParser';
const DEFAULT_COLOR = 0xc9d1d9; const DEFAULT_COLOR = 0xc9d1d9;
const ARC_SEGS = 64; const ARC_SEGS = 64;
@@ -1566,7 +1566,8 @@ export class Viewer2D {
} }
if (!ltName || ltName === 'Continuous' || ltName === 'ByLayer' || ltName === 'ByBlock') return null; if (!ltName || ltName === 'Continuous' || ltName === 'ByLayer' || ltName === 'ByBlock') return null;
const pattern = getLinPattern(ltName); const upperKey = ltName.toUpperCase().trim();
const pattern = this._dwgLinPatterns?.get(upperKey) || getLinPattern(ltName);
if (!pattern) return null; if (!pattern) return null;
const entScale = (e.entityHeader?.linetypeScale > 0 ? e.entityHeader.linetypeScale : 1); const entScale = (e.entityHeader?.linetypeScale > 0 ? e.entityHeader.linetypeScale : 1);
@@ -1750,10 +1751,11 @@ export class Viewer2D {
} else if (elem.type === 'text') { } else if (elem.type === 'text') {
const pState = getPathState(s); const pState = getPathState(s);
if (elem.text && pendingTexts) { if (elem.text && pendingTexts) {
// 1. Text Height: Exact text style DWG table height if fixed, else linetype elem.scale // 1. Data-driven Text Height: (fixedH * elem.scale) or (elem.scale * 0.5) to match arrowhead scale ratio
const sName = (elem.style || 'STANDARD').toUpperCase(); const sName = (elem.style || 'STANDARD').toUpperCase();
const fixedH = this._textStyleHeightMap?.get(sName) || 0; const fixedH = this._textStyleHeightMap?.get(sName) || 0;
const baseH = fixedH > 0 ? fixedH : (elem.scale || 0.2); const sFactor = (elem.scale > 0 ? elem.scale * 0.5 : 0.1);
const baseH = fixedH > 0 ? (fixedH * sFactor) : sFactor;
const h = baseH * ltscale; const h = baseH * ltscale;
let angle = pState.angle + ((elem.rotation || 0) * Math.PI) / 180; let angle = pState.angle + ((elem.rotation || 0) * Math.PI) / 180;
@@ -1771,18 +1773,24 @@ export class Viewer2D {
const sinT = Math.sin(angle); const sinT = Math.sin(angle);
const xo = (elem.xOffset || 0) * ltscale; const xo = (elem.xOffset || 0) * ltscale;
// alignV: 2 (middle) centers text quad on origin; set yo=0 so middle lies directly on grey arrow line axis // Vertical mid alignment: set yo = 0 so text middle lies directly on line center axis
const yo = 0; const yo = 0;
// 2. Exact Visual Midpoint: position text at exact midpoint of empty text gap (between arrowhead tip and next dash) // 2. Exact Visual Midpoint: determine primary text gap (previous vs next gap)
let gapMidOffset = 0; const prevElem = elements[(elemIdx - 2 + elements.length) % elements.length];
const nextElem = elements[elemIdx % elements.length]; const nextElem = elements[elemIdx % elements.length];
if (nextElem && nextElem.type === 'gap') {
const gapLen = Math.abs(nextElem.val || 0) * ltscale;
gapMidOffset = gapLen / 2;
}
const midState = gapMidOffset > 1e-6 ? getPathState(s + gapMidOffset) : pState; const prevGapLen = (prevElem && prevElem.type === 'gap') ? Math.abs(prevElem.val || 0) * ltscale : 0;
const nextGapLen = (nextElem && nextElem.type === 'gap') ? Math.abs(nextElem.val || 0) * ltscale : 0;
let midState = pState;
if (prevGapLen > nextGapLen && prevGapLen > 1e-6) {
// Text belongs inside previous gap (e.g. H-DICHL06R reverse mode S2 = -0.99)
midState = getPathState(s - prevGapLen / 2);
} else if (nextGapLen > 1e-6) {
// Text belongs inside next gap (e.g. H-DICHL06 forward mode S3 = -0.74)
midState = getPathState(s + nextGapLen / 2);
}
const tx = midState.x + (xo * cosT - yo * sinT); const tx = midState.x + (xo * cosT - yo * sinT);
const ty = midState.y + (xo * sinT + yo * cosT); const ty = midState.y + (xo * sinT + yo * cosT);
@@ -1793,8 +1801,8 @@ export class Viewer2D {
height: Math.max(h, 0.01), height: Math.max(h, 0.01),
rotation: angle, rotation: angle,
color, color,
alignH: 1, // center horizontally in gap between arrowheads alignH: 1, // center horizontally in gap
alignV: 2, // center vertically on line axis alignV: 2, // center vertically on line axis (vertical mid)
}); });
} }
} }
@@ -1929,11 +1937,21 @@ export class Viewer2D {
// _resolveDash to turn an entity's linetype into a dash/gap size. // _resolveDash to turn an entity's linetype into a dash/gap size.
_buildLinetypeMap(result) { _buildLinetypeMap(result) {
this._ltPatterns = new Map(); this._ltPatterns = new Map();
this._dwgLinPatterns = new Map();
this._layerLtByName = new Map(); this._layerLtByName = new Map();
this._layerLtByHandle = new Map(); this._layerLtByHandle = new Map();
this._globalLtscale = (result?.vars?.ltscale > 0 ? result.vars.ltscale : 1); this._globalLtscale = (result?.vars?.ltscale > 0 ? result.vars.ltscale : 1);
for (const lt of (result?.tables?.lineTypes ?? [])) { for (const lt of (result?.tables?.lineTypes ?? [])) {
if (lt?.name) this._ltPatterns.set(lt.name, Array.isArray(lt.pattern) ? lt.pattern : []); if (lt?.name) {
const patArr = Array.isArray(lt.pattern) ? lt.pattern : [];
this._ltPatterns.set(lt.name, patArr);
if (patArr.length > 0) {
const merged = mergeDwgLinetypePattern(lt.name, patArr, lt.description);
if (merged) {
this._dwgLinPatterns.set(lt.name.toUpperCase().trim(), merged);
}
}
}
} }
for (const l of (result?.tables?.layers ?? [])) { for (const l of (result?.tables?.layers ?? [])) {
const name = l.name ?? l.layerName; const name = l.name ?? l.layerName;
+89
View File
@@ -299,3 +299,92 @@ export function getLinPattern(name: string): LinPattern | undefined {
export function getAllRegisteredPatterns(): Map<string, LinPattern> { export function getAllRegisteredPatterns(): Map<string, LinPattern> {
return globalRegistry; return globalRegistry;
} }
export function mergeDwgLinetypePattern(
ltName: string,
dwgPattern: number[],
description?: string
): LinPattern | undefined {
if (!ltName) return undefined;
const base = getLinPattern(ltName);
if (!dwgPattern || !dwgPattern.length) return base;
if (!base) {
const elements: LinElement[] = [];
let totalLength = 0;
for (const val of dwgPattern) {
if (val > 1e-6) {
elements.push({
type: 'dash',
val,
scale: 1,
rotation: 0,
isAbsoluteAngle: false,
isUprightAngle: false,
xOffset: 0,
yOffset: 0,
});
totalLength += val;
} else if (val < -1e-6) {
const absVal = Math.abs(val);
elements.push({
type: 'gap',
val: -absVal,
scale: 1,
rotation: 0,
isAbsoluteAngle: false,
isUprightAngle: false,
xOffset: 0,
yOffset: 0,
});
totalLength += absVal;
}
}
return {
name: ltName,
description: description || '',
elements,
patternLength: Math.max(totalLength, 0.1),
};
}
// Merge DWG numeric dash/gap values into base pattern elements
const newElements: LinElement[] = [];
let dwgValIdx = 0;
let totalLength = 0;
for (const elem of base.elements) {
if (elem.type === 'dash') {
const dwgVal =
dwgValIdx < dwgPattern.length && dwgPattern[dwgValIdx] > 0
? dwgPattern[dwgValIdx]
: elem.val || 0.05;
if (dwgValIdx < dwgPattern.length && dwgPattern[dwgValIdx] > 0) {
dwgValIdx++;
}
newElements.push({ ...elem, val: dwgVal });
totalLength += dwgVal;
} else if (elem.type === 'gap') {
const dwgVal =
dwgValIdx < dwgPattern.length && dwgPattern[dwgValIdx] < 0
? dwgPattern[dwgValIdx]
: elem.val;
if (dwgValIdx < dwgPattern.length && dwgPattern[dwgValIdx] < 0) {
dwgValIdx++;
}
const absVal = Math.abs(dwgVal || 0.1);
newElements.push({ ...elem, val: -absVal });
totalLength += absVal;
} else {
newElements.push({ ...elem });
}
}
return {
...base,
description: description || base.description,
elements: newElements,
patternLength: Math.max(totalLength, 0.1),
};
}