feat: enhance complex linetype rendering with DWG text style data, gap midpoint centering, and theme defaults

This commit is contained in:
minsung
2026-07-30 17:59:08 +09:00
parent 1a664f677a
commit 3d3e052f3e
20 changed files with 55456 additions and 16 deletions
+62 -12
View File
@@ -248,6 +248,7 @@ export class Viewer2D {
this._clear();
this._entityMeta = [];
this._buildLayerMap(result?.tables?.layers);
this._buildStyleMap(result?.tables?.styles || result?.tables?.textStyles);
this._buildLinetypeMap(result);
// Sheet orientation: the active model-space viewport's VIEWTWIST rotates the
// view so a drawing stored tilted in WCS (rotated survey sheets — header UCS
@@ -273,6 +274,13 @@ export class Viewer2D {
else this._activeSpaceHex = typeof sh === 'number' ? sh.toString(16) : String(sh).toLowerCase();
}
if (!opts.keepTheme) {
const spacesList = listCadSpaces(result);
const activeSp = spacesList.find((s) => s.handleHex === this._activeSpaceHex);
const isPaper = activeSp?.kind === 'paper';
this.setTheme(!isPaper);
}
const entities = result?.entities || [];
const lineVerts = [];
const lineColors = [];
@@ -1045,9 +1053,9 @@ export class Viewer2D {
// ── UI integration ─────────────────────────────────────────────────────────
/** Swap canvas background for theme. dark=true → near-black, false → light. */
/** Swap canvas background for theme. dark=true → near-black (Model), false → white (Layout). */
setTheme(dark) {
this._scene.background = new THREE.Color(dark ? 0x0a0b0d : 0xf7f6f3);
this._scene.background = new THREE.Color(dark ? 0x0a0b0d : 0xffffff);
if (this._gridVisible) this._rebuildGrid();
}
@@ -1697,12 +1705,25 @@ export class Viewer2D {
};
};
const elements = linPattern.elements || [];
const patternLen = linPattern.patternLength * ltscale;
if (patternLen < 1e-6) return;
if (patternLen < 1e-6 || !elements.length) return;
// Calculate distance from pattern start to shape (arrowhead) element if present
let shapeOffset = -1;
let accum = 0;
for (const e of elements) {
if (e.type === 'shape') {
shapeOffset = accum;
break;
}
if (e.type === 'dash') accum += Math.max((e.val || 0.05) * ltscale, 0.01 * ltscale);
else if (e.type === 'gap') accum += Math.abs(e.val || 0.1) * ltscale;
}
let s = 0;
let elemIdx = 0;
const elements = linPattern.elements;
let lastShapePos = -1;
while (s < totalLen) {
const elem = elements[elemIdx % elements.length];
@@ -1723,12 +1744,18 @@ export class Viewer2D {
const gapLen = Math.abs(elem.val || 0.1) * ltscale;
s += gapLen;
} else if (elem.type === 'shape') {
lastShapePos = s;
const pState = getPathState(s);
this._renderShapeSymbol(elem, pState, ltscale, color, pushSeg);
} else if (elem.type === 'text') {
const pState = getPathState(s);
if (elem.text && pendingTexts) {
const h = (elem.scale || 0.2) * ltscale;
// 1. Text Height: Exact text style DWG table height if fixed, else linetype elem.scale
const sName = (elem.style || 'STANDARD').toUpperCase();
const fixedH = this._textStyleHeightMap?.get(sName) || 0;
const baseH = fixedH > 0 ? fixedH : (elem.scale || 0.2);
const h = baseH * ltscale;
let angle = pState.angle + ((elem.rotation || 0) * Math.PI) / 180;
if (elem.isAbsoluteAngle) {
angle = ((elem.rotation || 0) * Math.PI) / 180;
@@ -1744,20 +1771,30 @@ export class Viewer2D {
const sinT = Math.sin(angle);
const xo = (elem.xOffset || 0) * ltscale;
const rawYo = (elem.yOffset != null && Math.abs(elem.yOffset) > 1e-4) ? Math.abs(elem.yOffset) : 0.02;
const yo = rawYo * ltscale;
// alignV: 2 (middle) centers text quad on origin; set yo=0 so middle lies directly on grey arrow line axis
const yo = 0;
const tx = pState.x + (xo * cosT - yo * sinT);
const ty = pState.y + (xo * sinT + yo * cosT);
// 2. Exact Visual Midpoint: position text at exact midpoint of empty text gap (between arrowhead tip and next dash)
let gapMidOffset = 0;
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 tx = midState.x + (xo * cosT - yo * sinT);
const ty = midState.y + (xo * sinT + yo * cosT);
pendingTexts.push({
text: elem.text,
pos: { x: tx, y: ty, z: pState.z },
pos: { x: tx, y: ty, z: midState.z },
height: Math.max(h, 0.01),
rotation: angle,
color,
alignH: 1,
alignV: 2,
alignH: 1, // center horizontally in gap between arrowheads
alignV: 2, // center vertically on line axis
});
}
}
@@ -1846,6 +1883,19 @@ export class Viewer2D {
}
}
_buildStyleMap(styles) {
if (!this._textStyleHeightMap) this._textStyleHeightMap = new Map();
this._textStyleHeightMap.clear();
if (!styles || !Array.isArray(styles)) return;
for (const st of styles) {
const sName = (st.name || st.styleName || '').toUpperCase();
const h = st.height ?? st.fixedTextHeight ?? 0;
if (sName && h > 0) {
this._textStyleHeightMap.set(sName, h);
}
}
}
_buildLayerMap(layers) {
this._layerByHandle.clear();
this._layerByName.clear();