feat: 배경 테마(다크/라이트)에 따른 ACI 7번 색상 동적 변경 적용

This commit is contained in:
minsung
2026-07-31 10:06:11 +09:00
parent 14d1f06927
commit f7564d4abf
4 changed files with 74 additions and 44 deletions
+16 -2
View File
@@ -203,14 +203,24 @@ document.getElementById('file')!.addEventListener('change', (ev) => {
(ev.target as HTMLInputElement).value = ''; (ev.target as HTMLInputElement).value = '';
}); });
let dark = true; let dark = true;
document.getElementById('fit')?.addEventListener('click', () => {
viewer.fit();
});
document.getElementById('theme')!.addEventListener('click', () => { document.getElementById('theme')!.addEventListener('click', () => {
dark = !dark; dark = !dark;
viewer.setTheme(dark); viewer.setTheme(dark);
fillLayers();
}); });
document.getElementById('layersBtn')!.addEventListener('click', () => { document.getElementById('layersBtn')!.addEventListener('click', () => {
layersPanel.style.display = layersPanel.style.display === 'block' ? 'none' : 'block'; layersPanel.style.display = layersPanel.style.display === 'block' ? 'none' : 'block';
}); });
window.addEventListener('keydown', (e) => {
if ((e.key === 'f' || e.key === 'F') && (e.target as HTMLElement)?.tagName !== 'INPUT') {
viewer.fit();
}
});
window.addEventListener('dragover', (e) => e.preventDefault()); window.addEventListener('dragover', (e) => e.preventDefault());
window.addEventListener('drop', (e) => { window.addEventListener('drop', (e) => {
e.preventDefault(); e.preventDefault();
@@ -219,7 +229,11 @@ window.addEventListener('drop', (e) => {
}); });
const model = new URLSearchParams(location.search).get('model'); const model = new URLSearchParams(location.search).get('model');
if (model) void run(model, () => loadUrl(model)); if (model) {
else void run('C0060202-001-평면및종단면도.dwg', () => loadUrl(civilFile, 'C0060202-001-평면및종단면도.dwg')); void run(model, () => loadUrl(model));
} else {
setStatus('ready');
}
window.addEventListener('resize', () => viewer.resize()); window.addEventListener('resize', () => viewer.resize());
+1 -1
View File
@@ -5,7 +5,7 @@ import type { CadSpace } from './cadSpaces';
export class Viewer2D { export class Viewer2D {
constructor(container: HTMLElement); constructor(container: HTMLElement);
/** Render a DWG/DXF parseResult. `keepView` preserves pan/zoom on re-render. */ /** Render a DWG/DXF parseResult. `keepView` preserves pan/zoom on re-render. */
load(result: unknown, opts?: { keepView?: boolean; spaceHandle?: string | number | null }): void; load(result: unknown, opts?: { keepView?: boolean; keepTheme?: boolean; spaceHandle?: string | number | null }): void;
/** Zoom-extents to the loaded drawing. */ /** Zoom-extents to the loaded drawing. */
fit(): void; fit(): void;
/** Resync renderer/camera to the container size (call after un-hiding). */ /** Resync renderer/camera to the container size (call after un-hiding). */
+36 -21
View File
@@ -50,6 +50,8 @@ export class Viewer2D {
this._scene = new THREE.Scene(); this._scene = new THREE.Scene();
this._scene.background = new THREE.Color(0x0d1117); this._scene.background = new THREE.Color(0x0d1117);
this._isDark = true;
this._isLoading = false;
const w = container.clientWidth || 800; const w = container.clientWidth || 800;
const h = container.clientHeight || 600; const h = container.clientHeight || 600;
@@ -243,10 +245,29 @@ export class Viewer2D {
} }
load(result, opts = {}) { load(result, opts = {}) {
this._isLoading = true;
try {
this._lastResult = result; this._lastResult = result;
this._selMeta = null; this._selMeta = null;
this._clear(); this._clear();
this._entityMeta = []; this._entityMeta = [];
// Model vs Paper (Layout): never draw both spaces at once.
// - opts.spaceHandle provided (including null) → set active space
// - omitted → keep previous _activeSpaceHex if still valid for this file
if (Object.prototype.hasOwnProperty.call(opts, 'spaceHandle')) {
const sh = opts.spaceHandle;
if (sh == null || sh === '') this._activeSpaceHex = null;
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);
}
this._buildLayerMap(result?.tables?.layers); this._buildLayerMap(result?.tables?.layers);
this._buildStyleMap(result?.tables?.styles || result?.tables?.textStyles); this._buildStyleMap(result?.tables?.styles || result?.tables?.textStyles);
this._buildLinetypeMap(result); this._buildLinetypeMap(result);
@@ -265,22 +286,6 @@ export class Viewer2D {
this._pickFills = []; this._pickFills = [];
this._pickCurIdx = -1; this._pickCurIdx = -1;
// Model vs Paper (Layout): never draw both spaces at once.
// - opts.spaceHandle provided (including null) → set active space
// - omitted → keep previous _activeSpaceHex if still valid for this file
if (Object.prototype.hasOwnProperty.call(opts, 'spaceHandle')) {
const sh = opts.spaceHandle;
if (sh == null || sh === '') this._activeSpaceHex = null;
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 entities = result?.entities || [];
const lineVerts = []; const lineVerts = [];
const lineColors = []; const lineColors = [];
@@ -1049,14 +1054,24 @@ export class Viewer2D {
const fitBox = box.isEmpty() ? null : (this._trimmedContentBox(fitSamples, box) || box); const fitBox = box.isEmpty() ? null : (this._trimmedContentBox(fitSamples, box) || box);
this._contentBox = fitBox ? fitBox.clone() : null; this._contentBox = fitBox ? fitBox.clone() : null;
if (!opts.keepView && fitBox && !fitBox.isEmpty()) this._fit(fitBox); if (!opts.keepView && fitBox && !fitBox.isEmpty()) this._fit(fitBox);
} finally {
this._isLoading = false;
}
} }
// ── UI integration ───────────────────────────────────────────────────────── // ── UI integration ─────────────────────────────────────────────────────────
/** Swap canvas background for theme. dark=true → near-black (Model), false → white (Layout). */ /** Swap canvas background for theme. dark=true → near-black (Model), false → white (Layout). */
setTheme(dark) { setTheme(dark) {
this._scene.background = new THREE.Color(dark ? 0x0a0b0d : 0xffffff); const isDarkBool = !!dark;
const changed = (this._isDark !== isDarkBool);
this._isDark = isDarkBool;
this._scene.background = new THREE.Color(this._isDark ? 0x0a0b0d : 0xffffff);
if (this._gridVisible) this._rebuildGrid(); if (this._gridVisible) this._rebuildGrid();
if (changed && this._lastResult && !this._isLoading) {
this.load(this._lastResult, { keepView: true, keepTheme: true, spaceHandle: this._activeSpaceHex });
}
} }
setGrid(visible) { setGrid(visible) {
@@ -1547,7 +1562,7 @@ export class Viewer2D {
const aci = Math.abs(l.colorIndex ?? l.color ?? l.colorNumber ?? 7); const aci = Math.abs(l.colorIndex ?? l.color ?? l.colorNumber ?? 7);
return { return {
name, name,
colorHex: '#' + ((aciToHex(aci) ?? DEFAULT_COLOR).toString(16).padStart(6, '0')), colorHex: '#' + ((aciToHex(aci, this._isDark) ?? DEFAULT_COLOR).toString(16).padStart(6, '0')),
count: counts.get(name) || 0, count: counts.get(name) || 0,
visible: !hidden.has(name), visible: !hidden.has(name),
}; };
@@ -1914,7 +1929,7 @@ export class Viewer2D {
const handle = l.handle?.value ?? l.handle; const handle = l.handle?.value ?? l.handle;
const name = l.name ?? l.layerName; const name = l.name ?? l.layerName;
const aci = Math.abs(l.colorIndex ?? l.color ?? l.colorNumber ?? 7); const aci = Math.abs(l.colorIndex ?? l.color ?? l.colorNumber ?? 7);
const hex = aciToHex(aci) ?? DEFAULT_COLOR; const hex = aciToHex(aci, this._isDark) ?? DEFAULT_COLOR;
if (handle != null) this._layerByHandle.set(String(handle), hex); if (handle != null) this._layerByHandle.set(String(handle), hex);
if (name) this._layerByName.set(name, hex); if (name) this._layerByName.set(name, hex);
if (handle != null && name) this._layerNameByHandle.set(String(handle), name); if (handle != null && name) this._layerNameByHandle.set(String(handle), name);
@@ -2006,7 +2021,7 @@ export class Viewer2D {
const aci = entity.entityHeader?.colorIndex const aci = entity.entityHeader?.colorIndex
?? entity.color ?? entity.colorIndex ?? entity.colorNumber ?? 256; ?? entity.color ?? entity.colorIndex ?? entity.colorNumber ?? 256;
if (aci != null) { if (aci != null) {
const hex = aciToHex(aci); const hex = aciToHex(aci, this._isDark);
if (hex !== null) return hex; if (hex !== null) return hex;
} }
const lh = entity.layerHandle?.value ?? entity.layerHandle; const lh = entity.layerHandle?.value ?? entity.layerHandle;
@@ -2900,7 +2915,7 @@ export class Viewer2D {
} else if (t.bgColorRgb && (t.bgColorRgb.r != null)) { } else if (t.bgColorRgb && (t.bgColorRgb.r != null)) {
bgColor = ((t.bgColorRgb.r & 255) << 16) | ((t.bgColorRgb.g & 255) << 8) | (t.bgColorRgb.b & 255); bgColor = ((t.bgColorRgb.r & 255) << 16) | ((t.bgColorRgb.g & 255) << 8) | (t.bgColorRgb.b & 255);
} else if (t.bgColorIndex != null) { } else if (t.bgColorIndex != null) {
const hex = aciToHex(t.bgColorIndex); const hex = aciToHex(t.bgColorIndex, this._isDark);
if (hex != null) bgColor = hex; if (hex != null) bgColor = hex;
} }
+2 -1
View File
@@ -47,8 +47,9 @@ const VARIANTS_SL = [
[0.00, 0.30], // 9: dark gray [0.00, 0.30], // 9: dark gray
]; ];
export function aciToHex(aci) { export function aciToHex(aci, isDark = true) {
if (aci == null || aci === 0 || aci === 256) return null; if (aci == null || aci === 0 || aci === 256) return null;
if (aci === 7) return isDark ? 0xFFFFFF : 0x000000;
if (aci <= 9) return FIXED[aci] ?? null; if (aci <= 9) return FIXED[aci] ?? null;
if (aci >= 250 && aci <= 255) { if (aci >= 250 && aci <= 255) {
const v = Math.round(40 + (aci - 250) * 43); const v = Math.round(40 + (aci - 250) * 43);