UNPKG

@inweb/viewer-visualize

Version:

JavaScript library for rendering CAD and BIM files in a browser using VisualizeJS

1,235 lines (1,227 loc) 193 kB
/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a // license agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// import { CANVAS_EVENTS, draggersRegistry, commandsRegistry, componentsRegistry, Loader, loadersRegistry, Options, Info } from '@inweb/viewer-core'; export * from '@inweb/viewer-core'; import { EventEmitter2 } from '@inweb/eventemitter2'; import { Markup } from '@inweb/markup'; export * from '@inweb/markup'; class OdaGeAction { constructor(module) { this.setViewParams = (params) => { var _a; const extView = this.m_module.getViewer().getActiveTvExtendedView(); extView.setView(params.position, params.target, params.upVector, params.viewFieldWidth, params.viewFieldHeight, params.perspective); (_a = extView.delete) === null || _a === void 0 ? void 0 : _a.call(extView); }; this.getViewParams = () => { var _a; const view = this.m_module.getViewer().activeView; const obj = { position: view.viewPosition, target: view.viewTarget, upVector: view.upVector, viewFieldWidth: view.viewFieldWidth, viewFieldHeight: view.viewFieldHeight, perspective: view.perspective, }; (_a = view.delete) === null || _a === void 0 ? void 0 : _a.call(view); return obj; }; this.m_module = module; } getViewer() { return this.m_module.getViewer(); } getModel() { return this.getViewer().getMarkupModel(); } copyPoint(point) { const p = new this.m_module.Point3d(); p.set(point.x, point.y, point.z); return p; } createVector3d() { return new this.m_module.Vector3d(); } createPoint3d() { return new this.m_module.Point3d(); } createMatrix3d() { return new this.m_module.Matrix3d(); } createPlane() { return new this.m_module.OdTvPlane(); } toVector(geVector) { return this.m_module.Vector3d.createFromArray(geVector); } toGeVector(v) { return [v.x, v.y, v.z]; } toGePoint(point) { return [point.x, point.y, point.z]; } toPoint(gePoint) { return this.m_module.Point3d.createFromArray(gePoint); } screenToWorld(x, y) { return this.toPoint(this.m_module.getViewer().screenToWorld(x, y)); } toDoubleArray(points) { const p = []; for (let i = 0; i < points.length; i++) { p.push(points[i].x); p.push(points[i].y); p.push(points[i].z); } return p; } correctCameraTarget() { const params = this.getViewParams(); const ext = this.m_module.getViewer().getActiveExtents(); const { min, max } = ext; const target = this.toPoint(params.target); const contains = target.x >= min.x && target.y >= min.y && target.z >= min.z && target.x <= max.x && target.y <= max.y && target.z <= max.z; if (!contains) { params.target = ext.center(); this.setViewParams(params); } } } const CLICK_DELTA = 5; const INTERACTIVITY_FPS = 24; class OdBaseDragger extends OdaGeAction { constructor(subject) { super(subject.visualizeJs); this.beginInteractivity = () => { const viewer = this.getViewer(); const view = viewer.activeView; if (view["beginInteractivity"]) { view.beginInteractivity(INTERACTIVITY_FPS); this.subject.update(); } view.delete(); }; this.endInteractivity = () => { const viewer = this.getViewer(); const view = viewer.activeView; if (view["endInteractivity"]) { view.endInteractivity(); const device = this.getViewer().getActiveDevice(); const canvas = this.m_module.canvas; device.invalidate([0, 0, canvas.width, canvas.height]); device.delete(); this.subject.update(); } view.delete(); }; this.subject = subject; this.needInputText = false; this.mouseDownPosition = { x: 0, y: 0 }; this.autoSelect = false; this.onmessage = (event) => this.subject.emitEvent(event); this.canvasEvents = CANVAS_EVENTS; } initialize() { this.canvasEvents = this.canvasEvents.filter((x) => typeof this[x] === "function"); this.canvasEvents.forEach((x) => (this[x] = this[x].bind(this))); this.canvasEvents.forEach((x) => this.subject.on(x, this[x])); this.getViewer().setEnableAutoSelect(!!this.autoSelect); } dispose() { this.canvasEvents.forEach((x) => this.subject.off(x, this[x])); } relativeCoords(event) { return { x: event.offsetX * window.devicePixelRatio, y: event.offsetY * window.devicePixelRatio }; } pointerdown(ev) { if (!ev.isPrimary || OdBaseDragger.isGestureActive) { return; } ev.target.setPointerCapture(ev.pointerId); const relCoord = this.relativeCoords(ev); this.isDragging = true; this.mouseDownPosition = { x: relCoord.x, y: relCoord.y }; this.start(relCoord.x, relCoord.y, ev.clientX, ev.clientY); this.subject.update(); } pointerup(ev) { if (OdBaseDragger.needSkipPointerUp) { return; } if (!ev.isPrimary) { return; } ev.target.releasePointerCapture(ev.pointerId); const relCoord = this.relativeCoords(ev); this.end(relCoord.x, relCoord.y, ev.clientX, ev.clientY); this.isDragging = false; this.subject.update(); } pointercancel(ev) { if (!ev.isPrimary) { return; } this.m_module.canvas.dispatchEvent(new PointerEvent("pointerup", ev)); } pointermove(ev) { if (!ev.isPrimary || OdBaseDragger.isGestureActive) { return; } const relCoord = this.relativeCoords(ev); this.drag(relCoord.x, relCoord.y, ev.clientX, ev.clientY); if (this.isDragging) { this.subject.update(); } } click(ev) { const viewer = this.getViewer(); const relCoord = this.relativeCoords(ev); const x = relCoord.x; const y = relCoord.y; const isNotDragging = Math.abs(x - this.mouseDownPosition.x) < CLICK_DELTA && Math.abs(y - this.mouseDownPosition.y) < CLICK_DELTA; if (viewer && viewer.getEnableAutoSelect() && isNotDragging) { viewer.unselect(); viewer.select(x, y, x, y); this.subject.update(); const selectionSet = viewer.getSelected(); this.onmessage({ type: "select", data: selectionSet, handles: this.subject.getSelected() }); this.onmessage({ type: "select2", data: selectionSet, handles: this.subject.getSelected2() }); } } dblclick(ev) { const viewer = this.getViewer(); const relCoord = this.relativeCoords(ev); const x = relCoord.x; const y = relCoord.y; const device = viewer.getActiveDevice(); const clickView = device.viewAt([x, y]); if (clickView && !clickView.active) { viewer.activeView = clickView; clickView.delete(); this.subject.update(); } else { if (viewer && viewer.getEnableAutoSelect()) { const pSelected = viewer.getSelected(); if (!pSelected.isNull() && pSelected.numItems() !== 0) { const itr = pSelected.getIterator(); const entity = itr.getEntity(); viewer.zoomToEntity(entity); this.onmessage({ type: "zoomtoentity", data: entity }); this.subject.update(); this.deleteAll([itr, entity]); } } } device.delete(); } start(x, y, absoluteX = 0, absoluteY = 0) { } drag(x, y, absoluteX = 0, absoluteY = 0) { } end(x, y, absoluteX = 0, absoluteY = 0) { } getActiveMarkupEntity(entityName) { return this.subject.addMarkupEntity(entityName); } syncOverlayView() { return this.subject.syncOverlay(); } deleteAll(objects) { var _a; for (const obj of objects) { (_a = obj === null || obj === void 0 ? void 0 : obj.delete) === null || _a === void 0 ? void 0 : _a.call(obj); } } updatePreview() { } static set isGestureActive(value) { if (OdBaseDragger._isGestureActive === value) { return; } OdBaseDragger._isGestureActive = value; if (OdBaseDragger._isGestureActive) { OdBaseDragger.needSkipPointerUp = true; } } static get isGestureActive() { return OdBaseDragger._isGestureActive; } static get needSkipPointerUp() { if (OdBaseDragger._needSkipPointerUp) { OdBaseDragger.needSkipPointerUp = false; return true; } return false; } static set needSkipPointerUp(value) { OdBaseDragger._needSkipPointerUp = value; } } OdBaseDragger._isGestureActive = false; OdBaseDragger._needSkipPointerUp = false; function createHtmlElementIfNeed(element, targetElement, dataTestId) { if (!element) { element = document.createElement("div"); element.setAttribute("data-testid", dataTestId); targetElement.appendChild(element); } return element; } function destroyHtmlElement(element, targetElement) { if (element) { targetElement.removeChild(element); } return null; } function worldToScreen(gePoint, moduleInstance, viewer) { const worldPoint = moduleInstance.Point3d.createFromArray(gePoint); const avp = viewer.activeView; const mtx = avp.worldToDeviceMatrix; const devicePoint = worldPoint.transformBy(mtx); const res = { x: devicePoint.x / window.devicePixelRatio, y: devicePoint.y / window.devicePixelRatio }; mtx.delete(); worldPoint.delete(); devicePoint.delete(); avp.delete(); return res; } function getDistance(gePoint1, gePoint2, moduleInstance) { const tvPoint1 = moduleInstance.Point3d.createFromArray(gePoint1); const tvPoint2 = moduleInstance.Point3d.createFromArray(gePoint2); const distance = tvPoint1.distanceTo(tvPoint2).toFixed(2); tvPoint1.delete(); tvPoint2.delete(); return distance; } function normalizeFloat(value) { return value < 0 ? Math.ceil(value) : Math.floor(value); } const lineSegmentsIntersect = (p1, p2, p3, p4) => { const a_dx = p2.x - p1.x; const a_dy = p2.y - p1.y; const b_dx = p4.x - p3.x; const b_dy = p4.y - p3.y; const s = (-a_dy * (p1.x - p3.x) + a_dx * (p1.y - p3.y)) / (-b_dx * a_dy + a_dx * b_dy); const t = (+b_dx * (p1.y - p3.y) - b_dy * (p1.x - p3.x)) / (-b_dx * a_dy + a_dx * b_dy); return s >= 0 && s <= 1 && t >= 0 && t <= 1 ? { x: normalizeFloat(p1.x + t * a_dx), y: normalizeFloat(p1.y + t * a_dy), } : false; }; function checkSegmentsIntersect(p1, p2, p3, p4, res) { const r = lineSegmentsIntersect(p1, p2, p3, p4); if (r) { res.push(r); } } function isInsideRect(p, width, height) { return p.x <= width && p.x >= 0 && p.y <= height && p.y >= 0; } function getDataForDrawLineWithFixed(p1, p2, width, height) { const pLU = { x: 0, y: 0 }; const pRU = { x: width, y: 0 }; const pLB = { x: 0, y: height }; const pRB = { x: width, y: height }; const intersects = []; checkSegmentsIntersect(p1, p2, pLU, pRU, intersects); checkSegmentsIntersect(p1, p2, pLU, pLB, intersects); checkSegmentsIntersect(p1, p2, pLB, pRB, intersects); checkSegmentsIntersect(p1, p2, pRB, pRU, intersects); let fixedP1 = null; let fixedP2 = null; if (intersects.length === 0) { fixedP1 = p1; fixedP2 = p2; } else if (intersects.length === 1) { if (isInsideRect(p1, width, height)) { fixedP1 = p1; fixedP2 = intersects[0]; } else { fixedP1 = intersects[0]; fixedP2 = p2; } } else { fixedP1 = intersects[0]; fixedP2 = intersects[1]; } const dx = fixedP2.x - fixedP1.x; const dy = fixedP2.y - fixedP1.y; let angle = (180 * Math.atan(dy / dx)) / Math.PI; if (dx < 0) { angle -= 180; } const size = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); return { angle, width: size, p1: fixedP1, p2: fixedP2 }; } function onSetCallback(element, cb) { if (element) { element.onclick = cb ? () => cb() : () => { }; } } function onSetSelectivity(element, enable) { element.style.pointerEvents = enable ? "auto" : "none"; } class MeasureLineItem { constructor(targetElement, viewer, moduleInstance) { this.htmlElemStartPoint = null; this.htmlElemEndPoint = null; this.htmlElemLine = null; this.htmlElemTitle = null; this.startPoint = null; this.endPoint = null; this.scale = 1.0; this.unit = ""; this.precision = 2; this.size = 10.0; this.lineThickness = 2; this.style = { border: "2px solid #FFFFFF", background: "#009bff", color: "white", boxShadow: "0 0 10px rgba(0,0,0,0.5)", }; this.htmlElemStartPoint = createHtmlElementIfNeed(this.htmlElemStartPoint, targetElement, "ruler-start"); this.htmlElemEndPoint = createHtmlElementIfNeed(this.htmlElemEndPoint, targetElement, "ruler-end"); this.htmlElemLine = createHtmlElementIfNeed(this.htmlElemLine, targetElement, "ruler-line"); this.htmlElemTitle = createHtmlElementIfNeed(this.htmlElemTitle, targetElement, "ruler-value"); this.viewer = viewer; this.moduleInstance = moduleInstance; this.targetElement = targetElement; this.isFinishDraw = false; } drawMeasureLine() { const pointSize = this.size; const rect = this.moduleInstance.canvas.getBoundingClientRect(); if (this.startPoint) { this.htmlElemStartPoint = createHtmlElementIfNeed(this.htmlElemStartPoint, this.targetElement, "ruler-start"); const pScreenStart = worldToScreen(this.startPoint, this.moduleInstance, this.viewer); if (isInsideRect(pScreenStart, rect.width, rect.height)) { this.htmlElemStartPoint.style.display = "block"; this.htmlElemStartPoint.style.cursor = "pointer"; this.htmlElemStartPoint.style.position = "absolute"; this.htmlElemStartPoint.style.top = `${pScreenStart.y - pointSize / 2}px`; this.htmlElemStartPoint.style.left = `${pScreenStart.x - pointSize / 2}px`; this.htmlElemStartPoint.style.borderRadius = `${pointSize}px`; this.htmlElemStartPoint.style.border = this.style.border; this.htmlElemStartPoint.style.background = this.style.background; this.htmlElemStartPoint.style.zIndex = "2"; this.htmlElemStartPoint.style.width = `${pointSize}px`; this.htmlElemStartPoint.style.height = `${pointSize}px`; this.htmlElemStartPoint.style.boxShadow = this.style.boxShadow; } else { this.htmlElemStartPoint.style.display = "none"; } } if (this.endPoint && this.isFinishDraw) { this.htmlElemEndPoint = createHtmlElementIfNeed(this.htmlElemEndPoint, this.targetElement, "ruler-end"); const pScreenEnd = worldToScreen(this.endPoint, this.moduleInstance, this.viewer); if (isInsideRect(pScreenEnd, rect.width, rect.height)) { this.htmlElemEndPoint.style.display = "block"; this.htmlElemEndPoint.style.cursor = "pointer"; this.htmlElemEndPoint.style.position = "absolute"; this.htmlElemEndPoint.style.top = `${pScreenEnd.y - pointSize / 2}px`; this.htmlElemEndPoint.style.left = `${pScreenEnd.x - pointSize / 2}px`; this.htmlElemEndPoint.style.borderRadius = `${pointSize}px`; this.htmlElemEndPoint.style.border = this.style.border; this.htmlElemEndPoint.style.background = this.style.background; this.htmlElemEndPoint.style.zIndex = "2"; this.htmlElemEndPoint.style.width = `${pointSize}px`; this.htmlElemEndPoint.style.height = `${pointSize}px`; this.htmlElemEndPoint.style.boxShadow = this.style.boxShadow; } else { this.htmlElemEndPoint.style.display = "none"; } } if (this.endPoint && this.startPoint) { const point1 = worldToScreen(this.startPoint, this.moduleInstance, this.viewer); const point2 = worldToScreen(this.endPoint, this.moduleInstance, this.viewer); const { p1, p2, angle, width } = getDataForDrawLineWithFixed(point1, point2, rect.width, rect.height); const dx = p2.x - p1.x; const dy = p2.y - p1.y; const height = this.lineThickness; if (isInsideRect(p1, rect.width, rect.height) && isInsideRect(p2, rect.width, rect.height)) { this.htmlElemLine = createHtmlElementIfNeed(this.htmlElemLine, this.targetElement, "ruler-line"); this.htmlElemLine.style.display = "block"; this.htmlElemLine.style.cursor = "pointer"; this.htmlElemLine.style.position = "absolute"; this.htmlElemLine.style.top = `${p1.y}px`; this.htmlElemLine.style.left = `${p1.x}px`; this.htmlElemLine.style.width = `${width}px`; this.htmlElemLine.style.transform = `rotate(${angle}deg)`; this.htmlElemLine.style.transformOrigin = `0px ${height / 2}px`; this.htmlElemLine.style.boxShadow = this.style.boxShadow; this.htmlElemLine.style.border = "none"; this.htmlElemLine.style.background = this.style.background; this.htmlElemLine.style.zIndex = "1"; this.htmlElemLine.style.height = `${height}px`; const distance = this.getDistance(); const pX = p1.x + dx / 2; const pY = p1.y + dy / 2; this.htmlElemTitle = createHtmlElementIfNeed(this.htmlElemTitle, this.targetElement, "ruler-value"); this.htmlElemTitle.style.display = "block"; this.htmlElemTitle.style.cursor = "pointer"; this.htmlElemTitle.style.font = "10px"; this.htmlElemTitle.style.color = "white"; this.htmlElemTitle.style.position = "Absolute"; this.htmlElemTitle.style.top = `${pY}px`; this.htmlElemTitle.style.left = `${pX}px`; this.htmlElemTitle.style.transform = "translate(-50%, -50%)"; this.htmlElemTitle.style.borderRadius = "5px"; this.htmlElemTitle.style.boxShadow = this.style.boxShadow; this.htmlElemTitle.style.border = "none"; this.htmlElemTitle.style.background = this.style.background; this.htmlElemTitle.style.zIndex = "3"; this.htmlElemTitle.style.padding = "2px"; this.htmlElemTitle.style.textAlign = "center"; this.htmlElemTitle.innerHTML = this.formatDistance(distance); } else { this.htmlElemLine.style.display = "none"; this.htmlElemTitle.style.display = "none"; } } } getDistance() { let distance = getDistance(this.startPoint, this.endPoint, this.moduleInstance); if (Math.abs(this.scale) > 1e-10) distance /= this.scale; return distance; } calculatePrecision(value) { const distance = Math.abs(value); if (distance >= 1000) return 0; if (distance >= 10) return 1; if (distance >= 0.1) return 2; if (distance >= 0.001) return 3; return distance > 0 ? Math.floor(-Math.log10(distance)) + 1 : 2; } formatDistance(distance) { let digits; if (this.precision === "Auto") digits = this.calculatePrecision(distance); else if (Number.isFinite(this.precision)) digits = this.precision; else digits = parseFloat(this.precision); if (!Number.isFinite(digits)) digits = 2; else if (digits < 0) digits = 0; else if (digits > 10) digits = 10; let result = distance.toFixed(digits); if (this.precision === "Auto") result = result.replace(/\.0+$/, "").replace(/\.$/, ""); if (+result !== distance) result = "~ " + result; return `${result} ${this.unit}`; } setStartPoint(gePoint) { this.startPoint = gePoint; this.drawMeasureLine(); } setEndPoint(gePoint, isFinish) { this.isFinishDraw = isFinish === undefined ? true : isFinish; this.endPoint = gePoint; this.drawMeasureLine(); } update() { this.drawMeasureLine(); } setSize(size) { this.size = size; this.drawMeasureLine(); } clear() { this.endPoint = null; this.startPoint = null; this.htmlElemStartPoint = destroyHtmlElement(this.htmlElemStartPoint, this.targetElement); this.htmlElemEndPoint = destroyHtmlElement(this.htmlElemEndPoint, this.targetElement); this.htmlElemLine = destroyHtmlElement(this.htmlElemLine, this.targetElement); this.htmlElemTitle = destroyHtmlElement(this.htmlElemTitle, this.targetElement); } setUnit(unit) { this.unit = unit; this.drawMeasureLine(); } setConversionFactor(scale) { this.scale = scale; this.drawMeasureLine(); } setPrecision(precision) { this.precision = precision; this.drawMeasureLine(); } setStyle(style) { this.style = style; this.drawMeasureLine(); } setSelectionReactor(reactor) { onSetCallback(this.htmlElemStartPoint, reactor ? reactor.onStartPoint : null); onSetCallback(this.htmlElemEndPoint, reactor ? reactor.onEndPoint : null); onSetCallback(this.htmlElemTitle, reactor ? reactor.onTitle : null); } setSelectability(enable) { onSetSelectivity(this.htmlElemStartPoint, enable); onSetSelectivity(this.htmlElemEndPoint, enable); onSetSelectivity(this.htmlElemLine, enable); onSetSelectivity(this.htmlElemTitle, enable); } } function renameUnit(table, unit) { return table[unit] || unit; } class MeasureLineDragger extends OdBaseDragger { constructor(subject) { var _a, _b; super(subject); this.lineThickness = 2; this.press = false; this.gripingRadius = 5.0; this.firstPoint = null; this.secondPoint = null; this.rulerUnitTable = { Millimeters: "mm", Centimeters: "cm", Meters: "m", Feet: "ft", Inches: "in", Yards: "yd", Kilometers: "km", Miles: "mi", Micrometers: "µm", Mils: "mil", MicroInches: "µin", Default: "unit", }; this.rulerUnit = (_a = subject.options.rulerUnit) !== null && _a !== void 0 ? _a : "Default"; this.rulerPrecision = (_b = subject.options.rulerPrecision) !== null && _b !== void 0 ? _b : "Default"; this.items = []; this.canvasEvents.push("resize", "optionsChange"); } initialize() { super.initialize(); this.m_overlayElement = document.createElement("div"); this.m_overlayElement.style.background = "rgba(0,0,0,0)"; this.m_overlayElement.style.position = "fixed"; this.m_overlayElement.style.zIndex = "1"; this.m_overlayElement.style.pointerEvents = "none"; document.body.appendChild(this.m_overlayElement); this.subject.addEventListener("optionschange", this.optionsChange); this.resize(); } dispose() { super.dispose(); this.m_overlayElement.remove(); this.subject.removeEventListener("optionschange", this.optionsChange); } updatePreview() { this.items.forEach((item) => item.update()); } resize() { const rect = this.m_module.canvas.getBoundingClientRect(); this.m_overlayElement.style.top = `${rect.top}px`; this.m_overlayElement.style.left = `${rect.left}px`; this.m_overlayElement.style.width = `${rect.width}px`; this.m_overlayElement.style.height = `${rect.height}px`; } getSnapPointRadius() { const view = this.getViewer().activeView; const corners = view.viewDcCorners(); const pt1 = corners.lowerLeft; const pt2 = corners.upperRight; pt2[0] -= pt1[0]; pt2[1] -= pt1[1]; return Math.min(pt2[0], pt2[1]) / 120; } start(x, y) { this.createNewMeasureIfNeed(); const point = this.getViewer().getSnapPoint(x, y, this.gripingRadius); if (point) { this.firstPoint = point; this.previewMeasureLine.setStartPoint(this.firstPoint); } } drag(x, y) { this.createNewMeasureIfNeed(); const point = this.getViewer().getSnapPoint(x, y, this.gripingRadius); if (this.isDragging) { if (point) { if (this.firstPoint) { this.secondPoint = point; this.previewMeasureLine.setStartPoint(this.firstPoint); this.previewMeasureLine.setEndPoint(this.secondPoint, true); } else { this.firstPoint = point; this.previewMeasureLine.setStartPoint(this.firstPoint); } } else { this.secondPoint = null; this.previewMeasureLine.clear(); this.previewMeasureLine.setStartPoint(this.firstPoint); this.previewMeasureLine.setEndPoint(this.getViewer().screenToWorld(x, y), false); } } else { if (point) { this.previewMeasureLine.setStartPoint(point); } else { this.previewMeasureLine.clear(); } } } end() { if (this.firstPoint && this.secondPoint) { const newLineMeasure = this.createMeasureLine(); newLineMeasure.setStartPoint(this.firstPoint); newLineMeasure.setEndPoint(this.secondPoint, true); } this.firstPoint = null; this.secondPoint = null; this.previewMeasureLine.clear(); } createNewMeasureIfNeed() { if (!this.previewMeasureLine) { this.previewMeasureLine = this.createMeasureLine(); } } createMeasureLine() { const viewer = this.m_module.getViewer(); const item = new MeasureLineItem(this.m_overlayElement, viewer, this.m_module); item.lineThickness = this.lineThickness || item.lineThickness; const isDefaultUnit = this.rulerUnit === "Default"; const isDefaultPrecision = this.rulerPrecision === "Default"; item.setUnit(renameUnit(this.rulerUnitTable, isDefaultUnit ? viewer.getUnit() : this.rulerUnit)); if (!isDefaultUnit) { const fromUnit = this.getKUnitByName(viewer.getUnit()); const toUnit = this.getKUnitByName(this.subject.options.rulerUnit); const multiplier = viewer.getUnitsConversionCoef(fromUnit, toUnit); this.conversionFactor = 1 / multiplier; item.setConversionFactor(this.conversionFactor); } else { item.setConversionFactor(1.0); } if (!isDefaultPrecision) { item.setPrecision(this.rulerPrecision); } else { item.setPrecision(2); } this.items.push(item); return item; } optionsChange(event) { var _a, _b; const options = event.data; const toUnitName = (_a = options.rulerUnit) !== null && _a !== void 0 ? _a : "Default"; const toPrecision = (_b = options.rulerPrecision) !== null && _b !== void 0 ? _b : "Default"; const unitChanged = this.rulerUnit !== toUnitName; const precisionChanged = this.rulerPrecision !== toPrecision; if (!unitChanged && !precisionChanged) return; this.rulerUnit = toUnitName; this.rulerPrecision = toPrecision; const drawingUnit = this.m_module.getViewer().getUnit(); const eToUnit = this.getKUnitByName(toUnitName); const eFromUnit = this.getKUnitByName(drawingUnit); this.items.forEach((item) => { if (unitChanged) { if (toUnitName === "Default") { item.setUnit(renameUnit(this.rulerUnitTable, drawingUnit)); item.setConversionFactor(1.0); } else { item.setUnit(renameUnit(this.rulerUnitTable, toUnitName)); const multiplier = this.m_module.getViewer().getUnitsConversionCoef(eFromUnit, eToUnit); this.conversionFactor = 1 / multiplier; item.setConversionFactor(this.conversionFactor); } } if (precisionChanged) { if (toPrecision === "Default") { item.setPrecision(2); } else { item.setPrecision(toPrecision); } } }); } getKUnitByName(unitName) { let eUnit = this.m_module.Units.kUserDefined; switch (unitName) { case "Millimeters": eUnit = this.m_module.Units.kMillimeters; break; case "Centimeters": eUnit = this.m_module.Units.kCentimeters; break; case "Meters": eUnit = this.m_module.Units.kMeters; break; case "Feet": eUnit = this.m_module.Units.kFeet; break; case "Inches": eUnit = this.m_module.Units.kInches; break; case "Yards": eUnit = this.m_module.Units.kYards; break; case "Kilometers": eUnit = this.m_module.Units.kKilometers; break; case "Miles": eUnit = this.m_module.Units.kMiles; break; case "Micrometers": eUnit = this.m_module.Units.kMicrometers; break; case "Mils": eUnit = this.m_module.Units.kMils; break; case "MicroInches": eUnit = this.m_module.Units.kMicroInches; break; } return eUnit; } } class OdJoyStickDragger { constructor(global, container, callback, canvasElement) { this.hasEventListeners = false; const internalLineWidth = 2; const internalStrokeColor = "#003300"; const externalLineWidth = 2; const externalStrokeColor = "#35436E"; this.container = container; this.container.style.touchAction = "none"; this.canvas = document.createElement("canvas"); this.canvas.id = "odJoyStickCanvas"; this.canvas.width = 200; this.canvas.height = 200; this.container.appendChild(this.canvas); const context = this.canvas.getContext("2d"); let pressed = 0; const circumference = 2 * Math.PI; const internalRadius = (this.canvas.width - (this.canvas.width / 2 + 10)) / 2; const maxMoveStick = internalRadius + 5; const externalRadius = internalRadius + 30; const centerX = this.canvas.width / 2; const centerY = this.canvas.height / 2; let movedX = centerX; let movedY = centerY; this.onMouseDown = () => { event.preventDefault(); pressed = 1; }; this.onMouseMove = (event) => { event.preventDefault(); if (pressed === 1) { movedX = event.pageX; movedY = event.pageY; if (this.canvas.offsetParent && this.canvas.offsetParent.tagName.toUpperCase() === "BODY") { movedX -= this.canvas.offsetLeft; movedY -= this.canvas.offsetTop; } else if (this.canvas.offsetParent) { movedX -= this.canvas.offsetParent.offsetLeft; movedY -= this.canvas.offsetParent.offsetTop; } context.clearRect(0, 0, this.canvas.width, this.canvas.height); this.drawExternal(); this.drawInternal(); callback({ x: 100 * ((movedX - centerX) / maxMoveStick), y: 100 * ((movedY - centerY) / maxMoveStick) * -1, global, }); } }; this.onMouseUp = () => { event.preventDefault(); pressed = 0; movedX = centerX; movedY = centerY; context.clearRect(0, 0, this.canvas.width, this.canvas.height); this.drawExternal(); this.drawInternal(); callback({ x: 100 * ((movedX - centerX) / maxMoveStick), y: 100 * ((movedY - centerY) / maxMoveStick) * -1, global, }); }; this.drawExternal = () => { context.beginPath(); context.arc(centerX, centerY, externalRadius, 0, circumference, false); context.lineWidth = externalLineWidth; context.strokeStyle = externalStrokeColor; context.globalAlpha = 0.5; context.stroke(); }; this.drawInternal = () => { context.beginPath(); if (movedX < internalRadius) { movedX = maxMoveStick; } if (movedX + internalRadius > this.canvas.width) { movedX = this.canvas.width - maxMoveStick; } if (movedY < internalRadius) { movedY = maxMoveStick; } if (movedY + internalRadius > this.canvas.height) { movedY = this.canvas.height - maxMoveStick; } context.arc(movedX, movedY, internalRadius, 0, circumference, false); context.fillStyle = externalStrokeColor; context.lineWidth = internalLineWidth; context.strokeStyle = internalStrokeColor; context.globalAlpha = 0.5; context.fill(); context.stroke(); }; const addEventListeners = () => { if (!this.hasEventListeners) { this.canvas.addEventListener("pointerdown", this.onMouseDown, false); document.addEventListener("pointermove", this.onMouseMove, false); document.addEventListener("pointerup", this.onMouseUp, false); this.hasEventListeners = true; } }; const removeEventListeners = () => { if (this.hasEventListeners) { this.canvas.removeEventListener("pointerdown", this.onMouseDown, false); document.removeEventListener("pointermove", this.onMouseMove, false); document.removeEventListener("pointerup", this.onMouseUp, false); this.hasEventListeners = false; } }; const updateContainerPosition = () => { if (canvasElement) { const rect = canvasElement.getBoundingClientRect(); this.container.style.top = `${rect.height - 200}px`; this.container.style.left = `${rect.left}px`; this.container.style.width = `200px`; this.container.style.height = `200px`; } }; const updateVisibility = () => { const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); const isNarrowScreen = window.innerWidth < 1024; const shouldShow = isMobile || isNarrowScreen; if (shouldShow) { this.container.style.display = "block"; addEventListeners(); } else { this.container.style.display = "none"; removeEventListeners(); } }; this.onResize = () => { updateVisibility(); setTimeout(updateContainerPosition, 500); }; updateVisibility(); updateContainerPosition(); window.addEventListener("resize", this.onResize, false); this.drawExternal(); this.drawInternal(); } cleanup() { window.removeEventListener("resize", this.onResize, false); if (this.hasEventListeners) { this.canvas.removeEventListener("pointerdown", this.onMouseDown, false); document.removeEventListener("pointermove", this.onMouseMove, false); document.removeEventListener("pointerup", this.onMouseUp, false); this.hasEventListeners = false; } this.canvas.remove(); } } const FocalLengthConst$1 = 42.0; const calcFocalLength$1 = (lensLength, fieldWidth, fieldHeight) => { return (lensLength / FocalLengthConst$1) * Math.sqrt(fieldWidth * fieldWidth + fieldHeight * fieldHeight); }; class OdaWalkDragger extends OdBaseDragger { constructor(subject) { super(subject); this.viewer = undefined; this.multiplier = 5; this.baseSpeed = 1; this.keyPressMap = new Set(); this.keydown = this.keydown.bind(this); this.keyup = this.keyup.bind(this); this.lastFrameTS = 0; this.lastFrameJoyStickTS = 0; this.animationId = undefined; this.processMovement = this.processMovement.bind(this); this.processJoyStickMovement = this.processJoyStickMovement.bind(this); this.deltaAngle = Math.PI / 3600; this.autoSelect = true; this.isJoyStickMoving = false; this.addJoyStickDragger(subject.canvas.parentElement); } initialize() { super.initialize(); this.viewer = this.getViewer(); window.addEventListener("keydown", this.keydown, false); window.addEventListener("keyup", this.keyup, false); this.oldWCSEnableValue = this.viewer.getEnableWCS(); this.viewer.setEnableWCS(false); const view = this.viewer.activeView; const maxDimension = this.getMaxDimension(view); this.baseSpeed = maxDimension / 30000; this.subject.emitEvent({ type: "walkstart" }); this.viewParams = this.getViewParams(); this.setViewParams(this.viewParams); const model = this.viewer.getActiveModel(); this.cameraId = model.appendCamera("Camera0"); this.setupCamera(view); model.delete(); this.cameraWalker = new this.m_module.OdTvCameraWalker(); this.cameraWalker.setCamera(this.cameraId); this.subject.update(); this.enableZoomWheelPreviousValue = this.subject.options.enableZoomWheel; this.subject.options.enableZoomWheel = false; } dispose() { var _a; this.oldWCSEnableValue = this.oldWCSEnableValue !== undefined ? this.oldWCSEnableValue : this.subject.options.showWCS; this.viewer.setEnableWCS(this.oldWCSEnableValue); super.dispose(); this.keyPressMap.clear(); window.removeEventListener("keydown", this.keydown); window.removeEventListener("keyup", this.keyup); if (this.animationId) { window.cancelAnimationFrame(this.animationId); this.animationId = undefined; } if (this.cameraId) { const model = this.viewer.getActiveModel(); model.removeEntity(this.cameraId); model.delete(); (_a = this.cameraWalker) === null || _a === void 0 ? void 0 : _a.delete(); } if (this.viewParams) { this.setViewParams(this.viewParams); const avp = this.viewer.activeView; avp.delete(); } this.subject.update(true); this.subject.options.enableZoomWheel = this.enableZoomWheelPreviousValue; this.joyStickOverlayElement.remove(); this.joyStickDragger.cleanup(); } keydown(ev) { switch (ev.code) { case "NumpadSubtract": case "Minus": if (this.multiplier > 1) { this.multiplier = this.multiplier - 1; this.subject.emitEvent({ type: "walkspeedchange", data: this.multiplier }); } break; case "NumpadAdd": case "Equal": if (this.multiplier < 10) { this.multiplier = this.multiplier + 1; this.subject.emitEvent({ type: "walkspeedchange", data: this.multiplier }); } break; case "KeyW": case "KeyA": case "KeyS": case "KeyD": case "KeyQ": case "KeyE": this.keyPressMap.add(ev.code); if (!this.animationId) this.processMovement(0); break; } } keyup(ev) { this.keyPressMap.delete(ev.code); if (this.keyPressMap.size < 1 && this.animationId) { window.cancelAnimationFrame(this.animationId); this.animationId = undefined; this.lastFrameTS = 0; } } processMovement(timestamp) { this.animationId = requestAnimationFrame(this.processMovement); if (this.lastFrameTS !== 0) { const deltaTS = timestamp - this.lastFrameTS; if (deltaTS > 0) { const currentDelta = this.multiplier * deltaTS * this.baseSpeed; Array.from(this.keyPressMap).forEach((keyCode) => { switch (keyCode) { case "KeyW": this.moveForward(currentDelta); break; case "KeyS": this.moveBackward(currentDelta); break; case "KeyA": this.cameraWalker.moveLeft(currentDelta); break; case "KeyD": this.cameraWalker.moveRight(currentDelta); break; case "KeyQ": this.cameraWalker.moveUp(currentDelta); break; case "KeyE": this.cameraWalker.moveDown(currentDelta); break; } }); this.proceedChangeCamera(); } } this.lastFrameTS = timestamp; } start(x, y) { this.dragPosition = { x, y }; } drag(x, y) { if (this.cameraId && this.isDragging) { const dltX = x - this.dragPosition.x; const dltY = y - this.dragPosition.y; this.dragPosition = { x, y }; if (dltX !== 0.0) this.turnLeft(-dltX * this.deltaAngle); if (dltY !== 0.0) this.cameraWalker.turnDown(dltY * this.deltaAngle); this.subject.update(); this.subject.emitEvent({ type: "changecamera" }); } } moveForward(currentDelta) { const { Vector3d } = this.m_module; const camera = this.cameraWalker.camera().openObjectAsCamera(); const target = Vector3d.createFromArray(camera.target()); const dir = Vector3d.createFromArray(camera.direction()); const up = Vector3d.createFromArray(camera.upVector()); const pos = Vector3d.createFromArray(camera.position()); let move = Vector3d.createFromArray([dir.x, dir.y, 0]); if (Math.abs(dir.x) > 0.001 && Math.abs(dir.y) > 0.001) { move.setToProduct(move.normalize(), currentDelta); } else { move = Vector3d.createFromArray([0, currentDelta, 0]); } const newPos = pos.add(move); const newTarget = target.add(move); camera.setupCamera(newPos.toArray(), newTarget.toArray(), up.toArray()); } moveBackward(currentDelta) { this.moveForward(-currentDelta); } turnLeft(angle) { const pCamera = this.cameraWalker.camera().openObjectAsCamera(); const dir = this.toVector(pCamera.direction()); const up = this.toVector(pCamera.upVector()); const pos = pCamera.position(); const rotMatrix = this.createMatrix3d(); const zAxisVector = [0, 0, 1]; rotMatrix.setToRotation(angle, zAxisVector, pos); dir.transformBy(rotMatrix); up.transformBy(rotMatrix); pCamera.setupCameraByDirection(pos, dir.toArray(), up.toArray()); pCamera.delete(); } setupCamera(view) { const pCamera = this.cameraId.openObjectAsCamera(); const target = view.viewTarget; pCamera.setDisplayGlyph(false); pCamera.setDisplayTarget(false); pCamera.setAutoAdjust(true); pCamera.setupCamera(view.viewPosition, target, view.upVector); pCamera.setNearClip(false, 1.0); pCamera.setFarClip(false, 0); pCamera.setViewParameters(view.viewFieldWidth, view.viewFieldHeight, true); const focalL = calcFocalLength$1(view.lensLength, view.viewFieldWidth, view.viewFieldHeight); const pTarget = this.toPoint(view.viewTarget); const viewDir = this.toPoint(view.viewPosition); const viewDirSub = viewDir.sub(pTarget); const viewDirVec = viewDirSub.asVector(); const viewDirVecNormal = viewDirVec.normalize(); const geViewDir = this.toGeVector(viewDirVecNormal); const newGeViewDir = [geViewDir[0] * focalL, geViewDir[1] * focalL, geViewDir[2] * focalL]; const pTarget2 = this.toPoint(view.viewTarget); const newGeViewDirPt = this.toPoint(newGeViewDir); const newPos = pTarget2.add(newGeViewDirPt); pCamera.setupCamera(this.toGePoint(newPos), view.viewTarget, view.upVector); this.deleteAll([pTarget, viewDir, viewDirSub, viewDirVec, viewDirVecNormal, pTarget2, newGeViewDirPt, newPos]); pCamera.assignView(view); pCamera.delete(); } getMaxDimension(view) { const [xmax, ymax, zmax] = view.sceneExtents.max(); const [xmin, ymin, zmin] = view.sceneExtents.min(); const volume = [xmax - xmin, ymax - ymin, zmax - zmin]; return Math.max(...volume); } addJoyStickDragger(parentElement) { this.joyStickOverlayElement = document.createElement("div"); this.joyStickOverlayElement.id = "joyStickDiv"; this.joyStickOverlayElement.style.background = "rgba(0,0,0,0)"; this.joyStickOverlayElement.style.position = "fixed"; this.joyStickOverlayElement.style.zIndex = "0"; parentElement.appendChild(this.joyStickOverlayElement); this.joyStickDragger = new OdJoyStickDragger(this, this.joyStickOverlayElement, (stickData) => { if (Math.sqrt(stickData.x * stickData.x + stickData.y * stickData.y) > 20) { this.lastJoyStickCoord = { x: stickData.x, y: stickData.y }; if (!this.animationId && !this.isJoyStickMoving) { this.isJoyStickMoving = true; this.processJoyStickMovement(0); } } else { this.isJoyStickMoving = false; window.cancelAnimationFrame(this.animationId); this.animationId = undefined; this.l