fix: resolve workspace integration blockers (#5)
This commit is contained in:
Vendored
+9
-3
@@ -1,5 +1,4 @@
|
||||
// Types for the ported JS 2D engine (Viewer2D.js). Colocated so tsc resolves
|
||||
// `./Viewer2D.js` imports without allowJs. Only the methods the app uses are typed.
|
||||
// 포팅된 JavaScript 2D 엔진의 공개 interface입니다.
|
||||
export class Viewer2D {
|
||||
constructor(container: HTMLElement);
|
||||
/** Render a DWG/DXF parseResult. `keepView` preserves pan/zoom on re-render. */
|
||||
@@ -9,12 +8,19 @@ export class Viewer2D {
|
||||
/** Resync renderer/camera to the container size (call after un-hiding). */
|
||||
resize(): void;
|
||||
onSelect(cb: (entity: unknown) => void): void;
|
||||
setZoomSpeed(speed: number): void;
|
||||
getZoomSpeed(): number;
|
||||
setTheme(dark: boolean): void;
|
||||
setGrid(visible: boolean): void;
|
||||
getLayerInfo(): { name: string; colorHex: string; count: number; visible: boolean }[];
|
||||
setHiddenLayers(nameSet: Set<string>): void;
|
||||
getZoomPercent(): number;
|
||||
onViewChange(cb: () => void): void;
|
||||
onViewChange(cb: () => void): () => void;
|
||||
getStats(): { entities: number; blocks: number };
|
||||
snapshotWebPBlob(): Promise<Blob | null>;
|
||||
startMeasure(cb: (r: { phase: string; ax: number; ay: number; az: number; bx: number; by: number; bz: number; d: number }) => void): void;
|
||||
stopMeasure(): void;
|
||||
setAccent(hex: string): void;
|
||||
/** RAF, DOM listener, WebGL 자원과 canvas를 멱등으로 해제합니다. */
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ const ARC_SEGS = 64;
|
||||
const ELLIPSE_SEGS = 72;
|
||||
const CLICK_THRESHOLD_PX = 14;
|
||||
|
||||
function stripMText(s) {
|
||||
function stripMText(s) {
|
||||
if (!s) return '';
|
||||
return s
|
||||
.replace(/\\A\d+;/g, '') // \A1; vertical alignment
|
||||
@@ -30,11 +30,28 @@ function stripMText(s) {
|
||||
.replace(/^[ \t]+|[ \t]+$/gm, '') // trim each line's edges (keeps interior \n)
|
||||
.replace(/\n{3,}/g, '\n\n')
|
||||
.replace(/^\n+|\n+$/g, ''); // drop leading/trailing blank lines
|
||||
}
|
||||
|
||||
export class Viewer2D {
|
||||
constructor(container) {
|
||||
this._container = container;
|
||||
}
|
||||
|
||||
function disposeMaterial(material) {
|
||||
for (const item of (Array.isArray(material) ? material : [material])) {
|
||||
item?.map?.dispose();
|
||||
item?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
function disposeObject(root) {
|
||||
root?.traverse?.((object) => {
|
||||
object.geometry?.dispose();
|
||||
if (object.material) disposeMaterial(object.material);
|
||||
});
|
||||
}
|
||||
|
||||
export class Viewer2D {
|
||||
constructor(container) {
|
||||
this._disposed = false;
|
||||
this._rafId = null;
|
||||
this._viewChangeCallbacks = new Set();
|
||||
this._container = container;
|
||||
this._entityMeta = [];
|
||||
this._layerByHandle = new Map();
|
||||
this._layerByName = new Map();
|
||||
@@ -74,26 +91,43 @@ export class Viewer2D {
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', (e) => { if (e.key === 'f' || e.key === 'F') this.fit(); });
|
||||
this._handleKeyDown = (e) => {
|
||||
if (e.key === 'f' || e.key === 'F') this.fit();
|
||||
};
|
||||
window.addEventListener('keydown', this._handleKeyDown);
|
||||
|
||||
this._group = new THREE.Group();
|
||||
this._scene.add(this._group);
|
||||
|
||||
window.addEventListener('resize', () => this._onResize());
|
||||
// Prevent browser auto-scroll popup when middle-button is pressed on the WebGL canvas
|
||||
this._renderer.domElement.addEventListener('pointerdown', (e) => {
|
||||
if (e.button === 1) e.preventDefault();
|
||||
});
|
||||
this._renderer.domElement.addEventListener('mousedown', (e) => {
|
||||
this._downXY = [e.clientX, e.clientY];
|
||||
if (e.button === 1) e.preventDefault();
|
||||
});
|
||||
this._renderer.domElement.addEventListener('click', (e) => this._onClick(e));
|
||||
this._handleResize = () => this._onResize();
|
||||
window.addEventListener('resize', this._handleResize);
|
||||
// Prevent browser auto-scroll popup when middle-button is pressed on the WebGL canvas
|
||||
this._handlePointerDown = (e) => {
|
||||
if (e.button === 1) e.preventDefault();
|
||||
};
|
||||
this._handleMouseDown = (e) => {
|
||||
this._downXY = [e.clientX, e.clientY];
|
||||
if (e.button === 1) e.preventDefault();
|
||||
};
|
||||
this._handleClick = (e) => this._onClick(e);
|
||||
this._renderer.domElement.addEventListener('pointerdown', this._handlePointerDown);
|
||||
this._renderer.domElement.addEventListener('mousedown', this._handleMouseDown);
|
||||
this._renderer.domElement.addEventListener('click', this._handleClick);
|
||||
this._selColor = { r: 0xd8 / 255, g: 0x3a / 255, b: 0x2f / 255 };
|
||||
this._gridVisible = false;
|
||||
this._gridMesh = null;
|
||||
this._controls.addEventListener('change', () => { if (this._gridVisible) this._rebuildGrid(); });
|
||||
this._animate();
|
||||
this._handleControlsChange = () => {
|
||||
if (this._gridVisible) this._rebuildGrid();
|
||||
for (const callback of this._viewChangeCallbacks) callback();
|
||||
};
|
||||
this._controls.addEventListener('change', this._handleControlsChange);
|
||||
this._onAnimationFrame = () => {
|
||||
if (this._disposed) return;
|
||||
this._controls.update();
|
||||
this._renderer.render(this._scene, this._camera);
|
||||
this._rafId = requestAnimationFrame(this._onAnimationFrame);
|
||||
};
|
||||
this._animate();
|
||||
}
|
||||
|
||||
onSelect(cb) { this._onSelectCb = cb; }
|
||||
@@ -683,7 +717,11 @@ export class Viewer2D {
|
||||
getZoomPercent() { return Math.round((this._camera?.zoom ?? 1) * 100); }
|
||||
|
||||
/** Subscribe to pan/zoom changes (for live zoom readout). */
|
||||
onViewChange(cb) { this._controls?.addEventListener('change', cb); }
|
||||
onViewChange(cb) {
|
||||
if (this._disposed) return () => {};
|
||||
this._viewChangeCallbacks.add(cb);
|
||||
return () => this._viewChangeCallbacks.delete(cb);
|
||||
}
|
||||
|
||||
/** Hide entities on the given layer names (Set<string>); re-renders keeping the view. */
|
||||
setHiddenLayers(nameSet) {
|
||||
@@ -2328,7 +2366,7 @@ export class Viewer2D {
|
||||
return Infinity;
|
||||
}
|
||||
|
||||
_fit(box) {
|
||||
_fit(box) {
|
||||
const c = box.getCenter(new THREE.Vector3());
|
||||
const size = box.getSize(new THREE.Vector3());
|
||||
const w = this._container.clientWidth || 800;
|
||||
@@ -2346,15 +2384,57 @@ export class Viewer2D {
|
||||
this._camera.up.set(-Math.sin(a), Math.cos(a), 0);
|
||||
this._camera.updateProjectionMatrix();
|
||||
this._controls.target.set(c.x, c.y, 0);
|
||||
this._controls.update();
|
||||
}
|
||||
|
||||
_clear() {
|
||||
for (let i = this._group.children.length - 1; i >= 0; i--) {
|
||||
const o = this._group.children[i];
|
||||
o.geometry?.dispose();
|
||||
if (o.material) { o.material.map?.dispose(); o.material.dispose(); }
|
||||
this._group.remove(o);
|
||||
this._controls.update();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this._disposed) return;
|
||||
this._disposed = true;
|
||||
|
||||
if (this._rafId !== null) {
|
||||
cancelAnimationFrame(this._rafId);
|
||||
this._rafId = null;
|
||||
}
|
||||
|
||||
window.removeEventListener('keydown', this._handleKeyDown);
|
||||
window.removeEventListener('resize', this._handleResize);
|
||||
const canvas = this._renderer.domElement;
|
||||
canvas.removeEventListener('pointerdown', this._handlePointerDown);
|
||||
canvas.removeEventListener('mousedown', this._handleMouseDown);
|
||||
canvas.removeEventListener('click', this._handleClick);
|
||||
this._controls.removeEventListener('change', this._handleControlsChange);
|
||||
this._viewChangeCallbacks.clear();
|
||||
|
||||
this.stopMeasure();
|
||||
this.setGrid(false);
|
||||
this._clear();
|
||||
this._scene.remove(this._group);
|
||||
this._controls.dispose();
|
||||
this._renderer.dispose();
|
||||
this._renderer.forceContextLoss();
|
||||
canvas.remove();
|
||||
this._scene.clear();
|
||||
|
||||
this._lastResult = null;
|
||||
this._onSelectCb = null;
|
||||
this._measureCb = null;
|
||||
this._entityMeta = [];
|
||||
this._pickSegs = [];
|
||||
this._pickSegMeta = [];
|
||||
this._pickFills = [];
|
||||
this._layerByHandle.clear();
|
||||
this._layerByName.clear();
|
||||
this._layerNameByHandle?.clear();
|
||||
this._linetypeByName?.clear();
|
||||
this._hiddenLayers?.clear();
|
||||
this._texCache?.clear();
|
||||
}
|
||||
|
||||
_clear() {
|
||||
for (let i = this._group.children.length - 1; i >= 0; i--) {
|
||||
const o = this._group.children[i];
|
||||
disposeObject(o);
|
||||
this._group.remove(o);
|
||||
}
|
||||
// Shared text textures were disposed via the sprites above (re-dispose is a
|
||||
// no-op); drop the cache so the next load rasterizes fresh.
|
||||
@@ -2362,8 +2442,9 @@ export class Viewer2D {
|
||||
this._textGen++; // invalidate any in-flight async text flush
|
||||
}
|
||||
|
||||
_onResize() {
|
||||
const w = this._container.clientWidth, h = this._container.clientHeight;
|
||||
_onResize() {
|
||||
if (this._disposed) return;
|
||||
const w = this._container.clientWidth, h = this._container.clientHeight;
|
||||
if (!w || !h) return;
|
||||
this._renderer.setSize(w, h);
|
||||
// Frustum planes are CAMERA-LOCAL (relative to camera.position), not world coords.
|
||||
@@ -2375,9 +2456,8 @@ export class Viewer2D {
|
||||
this._camera.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
_animate() {
|
||||
requestAnimationFrame(() => this._animate());
|
||||
this._controls.update();
|
||||
this._renderer.render(this._scene, this._camera);
|
||||
}
|
||||
}
|
||||
_animate() {
|
||||
if (this._disposed || this._rafId !== null) return;
|
||||
this._rafId = requestAnimationFrame(this._onAnimationFrame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"type": "module",
|
||||
"description": "WASM wrapper around acadrust (MPL-2.0, used unmodified) emitting the hmwebviewer parseResult shape",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"license": "MPL-2.0",
|
||||
"files": [
|
||||
"acadrust_dwg_bg.wasm",
|
||||
"acadrust_dwg.js",
|
||||
|
||||
Reference in New Issue
Block a user