@inweb/viewer-visualize
Version:
JavaScript library for rendering CAD and BIM files in a browser using VisualizeJS
1,282 lines (1,258 loc) • 867 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.ODA = global.ODA || {}, global.ODA.Visualize = global.ODA.Visualize || {})));
})(this, (function (exports) { 'use strict';
class CommandsRegistry {
constructor() {
this._commands = new Map;
}
registerCommand(id, handler, description, thisArg) {
this._commands.set(id, {
id: id,
handler: handler,
thisArg: thisArg,
description: description
});
}
registerCommandAlias(id, alias) {
this.registerCommand(alias, ((viewer, ...args) => this.executeCommand(id, viewer, ...args)));
}
getCommand(id) {
return this._commands.get(id);
}
getCommands() {
const map = new Map;
this._commands.forEach(((value, key) => map.set(key, value)));
return map;
}
executeCommand(id, viewer, ...args) {
const command = this._commands.get(id);
if (!command) {
if (viewer) {
const isDraggerCommand = viewer.draggers.includes(id);
if (isDraggerCommand) return viewer.setActiveDragger(id);
}
console.warn(`Command '${id}' not found`);
return undefined;
}
const {handler: handler, thisArg: thisArg} = command;
const result = handler.apply(thisArg, [ viewer, ...args ]);
viewer === null || viewer === undefined ? undefined : viewer.emit({
type: "command",
data: id,
args: args
});
return result;
}
}
const _commandsRegistry = new Map;
function commandsRegistry(viewerType = "") {
let result = _commandsRegistry.get(viewerType);
if (!result) {
result = new CommandsRegistry;
_commandsRegistry.set(viewerType, result);
}
return result;
}
class Dragger {
constructor(viewer) {
this.name = "";
}
dispose() {}
}
class DraggersRegistry {
constructor() {
this._providers = new Map;
}
registerDragger(name, provider) {
this._providers.set(name, provider);
}
registerDraggerAlias(name, alias) {
const provider = this._providers.get(name);
if (provider) this.registerDragger(alias, (viewer => provider(viewer)));
}
getDraggers() {
const map = new Map;
this._providers.forEach(((value, key) => map.set(key, value)));
return map;
}
createDragger(name, viewer) {
const provider = this._providers.get(name);
if (!provider) return null;
const dragger = provider(viewer);
dragger.name = name;
return dragger;
}
}
const _draggersRegistry = new Map;
function draggersRegistry(viewerType = "") {
let result = _draggersRegistry.get(viewerType);
if (!result) {
result = new DraggersRegistry;
_draggersRegistry.set(viewerType, result);
}
return result;
}
class Component {
constructor(viewer) {
this.name = "";
}
dispose() {}
}
class Components {
constructor() {
this._providers = new Map;
}
registerComponent(name, provider) {
this._providers.set(name, provider);
}
registerComponentAlias(name, alias) {
const provider = this._providers.get(name);
if (provider) this.registerComponent(alias, (viewer => provider(viewer)));
}
getComponents() {
const map = new Map;
this._providers.forEach(((value, key) => map.set(key, value)));
return map;
}
createComponent(name, viewer) {
const provider = this._providers.get(name);
if (!provider) return null;
const component = provider(viewer);
component.name = name;
return component;
}
}
const _components = new Map;
function componentsRegistry(viewerType = "") {
let result = _components.get(viewerType);
if (!result) {
result = new Components;
_components.set(viewerType, result);
}
return result;
}
class Loader {
constructor() {
this.name = "";
this.abortController = new AbortController;
}
dispose() {
this.abortController.abort();
this.abortController = undefined;
}
isSupport(file, format) {
return false;
}
load(file, format, params) {
return Promise.resolve(this);
}
cancel() {
this.abortController.abort();
}
}
class Loaders {
constructor() {
this._providers = new Map;
}
registerLoader(name, provider) {
this._providers.set(name, provider);
}
getLoader(name) {
return this._providers.get(name);
}
getLoaders() {
const map = new Map;
this._providers.forEach(((value, key) => map.set(key, value)));
return map;
}
createLoader(viewer, file, format) {
let result = null;
this._providers.forEach(((provider, key) => {
const loader = provider(viewer);
if (loader.isSupport(file, format)) {
result = loader;
result.name = key;
}
}));
return result;
}
}
const _loaders = new Map;
function loadersRegistry(viewerType = "") {
let result = _loaders.get(viewerType);
if (!result) {
result = new Loaders;
_loaders.set(viewerType, result);
}
return result;
}
function defaultOptions() {
return {
showWCS: true,
cameraAnimation: true,
antialiasing: true,
groundShadow: false,
shadows: false,
cameraAxisXSpeed: 4,
cameraAxisYSpeed: 1,
ambientOcclusion: false,
enableStreamingMode: true,
enablePartialMode: false,
memoryLimit: 3294967296,
cuttingPlaneFillColor: {
red: 255,
green: 152,
blue: 0
},
edgesColor: {
r: 255,
g: 152,
b: 0
},
facesColor: {
r: 255,
g: 152,
b: 0
},
edgesVisibility: true,
edgesOverlap: true,
facesOverlap: false,
facesTransparancy: 200,
enableCustomHighlight: true,
sceneGraph: false,
edgeModel: true,
reverseZoomWheel: false,
enableZoomWheel: true,
enableGestures: true,
geometryType: "vsfx",
rulerUnit: "Default"
};
}
class Options {
constructor(emitter) {
this._emitter = emitter;
this._data = defaultOptions();
this.loadFromStorage();
}
static defaults() {
return defaultOptions();
}
notifierChangeEvent() {
console.warn("Options.notifierChangeEvent() has been deprecated since 25.3 and will be removed in a future release, use Options.change() instead.");
this.change();
}
change() {
if (this._emitter !== undefined) {
this.saveToStorage();
this._emitter.emit({
type: "optionschange",
data: this
});
}
}
saveToStorage() {
if (typeof window !== "undefined") try {
localStorage.setItem("od-client-settings", JSON.stringify(this.data));
} catch (error) {
console.error("Cannot save client settings.", error);
}
}
loadFromStorage() {
if (typeof window !== "undefined") try {
const item = localStorage.getItem("od-client-settings");
if (item) {
const data = JSON.parse(item);
this.data = {
...data
};
}
} catch (error) {
console.error("Cannot load client settings.", error);
}
}
resetToDefaults(fields) {
if (fields !== undefined) {
const defaults = Options.defaults();
const resetData = fields.reduce(((acc, field) => {
acc[field] = defaults[field];
return acc;
}), {});
this.data = {
...this.data,
...resetData
};
} else {
this.data = {
...this.data,
...Options.defaults()
};
}
}
get data() {
return this._data;
}
set data(value) {
const enablePartialMode = value.enableStreamingMode ? value.enablePartialMode : false;
const sceneGraph = enablePartialMode ? false : value.sceneGraph;
this._data = {
...Options.defaults(),
...this._data,
...value,
enablePartialMode: enablePartialMode,
sceneGraph: sceneGraph
};
this.change();
}
get showWCS() {
return this._data.showWCS;
}
set showWCS(value) {
this._data.showWCS = value;
this.change();
}
get cameraAnimation() {
return this._data.cameraAnimation;
}
set cameraAnimation(value) {
this._data.cameraAnimation = value;
this.change();
}
get antialiasing() {
return this._data.antialiasing;
}
set antialiasing(value) {
this._data.antialiasing = value;
this.change();
}
get groundShadow() {
return this._data.groundShadow;
}
set groundShadow(value) {
this._data.groundShadow = value;
this.change();
}
get shadows() {
return this._data.shadows;
}
set shadows(value) {
this._data.shadows = value;
this.change();
}
get cameraAxisXSpeed() {
return this._data.cameraAxisXSpeed;
}
set cameraAxisXSpeed(value) {
this._data.cameraAxisXSpeed = value;
this.change();
}
get cameraAxisYSpeed() {
return this._data.cameraAxisYSpeed;
}
set cameraAxisYSpeed(value) {
this.cameraAxisYSpeed = value;
this.change();
}
get ambientOcclusion() {
return this._data.ambientOcclusion;
}
set ambientOcclusion(value) {
this._data.ambientOcclusion = value;
this.change();
}
get enableStreamingMode() {
return this._data.enableStreamingMode;
}
set enableStreamingMode(value) {
this._data.enableStreamingMode = value;
if (!value) this._data.enablePartialMode = false;
this.change();
}
get enablePartialMode() {
return this._data.enablePartialMode;
}
set enablePartialMode(value) {
this._data.enablePartialMode = value;
if (value) {
this._data.enableStreamingMode = true;
this._data.sceneGraph = false;
}
this.change();
}
get memoryLimit() {
return this._data.memoryLimit;
}
set memoryLimit(value) {
this._data.memoryLimit = value;
this.change();
}
get cuttingPlaneFillColor() {
return this._data.cuttingPlaneFillColor;
}
set cuttingPlaneFillColor(value) {
this._data.cuttingPlaneFillColor = value;
this.change();
}
get edgesColor() {
return this._data.edgesColor;
}
set edgesColor(value) {
this._data.edgesColor = value;
this.change();
}
get facesColor() {
return this._data.facesColor;
}
set facesColor(value) {
this._data.facesColor = value;
this.change();
}
get edgesVisibility() {
return this._data.edgesVisibility;
}
set edgesVisibility(value) {
this._data.edgesVisibility = value;
this.change();
}
get edgesOverlap() {
return this._data.edgesOverlap;
}
set edgesOverlap(value) {
this._data.edgesOverlap = value;
this.change();
}
get facesOverlap() {
return this._data.facesOverlap;
}
set facesOverlap(value) {
this._data.facesOverlap = value;
this.change();
}
get facesTransparancy() {
return this._data.facesTransparancy;
}
set facesTransparancy(value) {
this._data.facesTransparancy = value;
this.change();
}
get enableCustomHighlight() {
return this._data.enableCustomHighlight;
}
set enableCustomHighlight(value) {
this._data.enableCustomHighlight = value;
this.change();
}
get sceneGraph() {
return this._data.sceneGraph;
}
set sceneGraph(value) {
this._data.sceneGraph = value;
if (value) this._data.enablePartialMode = false;
this.change();
}
get edgeModel() {
return Boolean(this._data.edgeModel);
}
set edgeModel(value) {
this._data.edgeModel = Boolean(value);
this.change();
}
get reverseZoomWheel() {
return this._data.reverseZoomWheel;
}
set reverseZoomWheel(value) {
this._data.reverseZoomWheel = !!value;
this.change();
}
get enableZoomWheel() {
return this._data.enableZoomWheel;
}
set enableZoomWheel(value) {
this._data.enableZoomWheel = !!value;
this.change();
}
get enableGestures() {
return this._data.enableGestures;
}
set enableGestures(value) {
this._data.enableGestures = !!value;
this.change();
}
get geometryType() {
return this._data.geometryType;
}
set geometryType(value) {
this._data.geometryType = value;
this.change();
}
get rulerUnit() {
return this._data.rulerUnit;
}
set rulerUnit(value) {
this._data.rulerUnit = value;
this.change();
}
}
const CanvasEvents = [ "click", "contextmenu", "dblclick", "mousedown", "mouseleave", "mousemove", "mouseup", "pointercancel", "pointerdown", "pointerleave", "pointermove", "pointerup", "touchcancel", "touchend", "touchmove", "touchstart", "wheel" ];
const CANVAS_EVENTS = CanvasEvents;
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 === undefined ? undefined : _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 === undefined ? undefined : _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);
}
}
}
///////////////////////////////////////////////////////////////////////////////
// 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.
///////////////////////////////////////////////////////////////////////////////
/* eslint-disable no-unused-vars */
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();
const handles = this.subject.getSelected();
this.onmessage({ type: "select", data: selectionSet, handles });
}
}
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 === undefined ? undefined : obj.delete) === null || _a === undefined ? undefined : _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";
}
///////////////////////////////////////////////////////////////////////////////
// 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.
///////////////////////////////////////////////////////////////////////////////
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.unit = "";
this.scale = 1.0;
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();
// draw start point
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";
}
}
// draw end point
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()} ${this.unit}`;
const pX = p1.x + dx / 2;
const pY = p1.y + dy / 2;
const widthTitle = distance.length * 10;
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 - widthTitle / 2}px`;
this.htmlElemTitle.style.width = `${widthTitle}px`;
this.htmlElemTitle.style.transformOrigin = "0px 0px";
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 = `${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 - 1.0) > 10e-5) {
distance = (distance / this.scale).toFixed(2);
}
return distance;
}
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();
}
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;
super(subject);
this.lineThickness = 2;
this.press = false;
this.gripingRadius = 5.0;
this.firstPoint = null;
this.secondPoint = null;
this.renameUnitTable = {
Millimeters: "mm",
Centimeters: "cm",
Meters: "m",
Feet: "ft",
Inches: "in",
Yards: "yd",
Kilometers: "km",
Miles: "mi",
Micrometers: "µm",
MicroInches: "µin",
Default: "unit",
};
this.items = [];
this.canvasEvents.push("resize");
this.oldRulerUnit = (_a = subject.options.rulerUnit) !== null && _a !== undefined ? _a : "Default";
this.optionsChange = this.optionsChange.bind(this);
}
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.subject.options.rulerUnit || this.subject.options.rulerUnit === "Default";
item.setUnit(renameUnit(this.renameUnitTable, isDefaultUnit ? viewer.getUnit() : this.subject.options.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);
}
this.items.push(item);
return item;
}
optionsChange(event) {
var _a;
const options = event.data;
const toUnitName = (_a = options.rulerUnit) !== null && _a !== undefined ? _a : "Default";
if (this.oldRulerUnit === toUnitName)
return;
this.oldRulerUnit = toUnitName;
const drawingUnit = this.m_module.getViewer().getUnit();
const eToUnit = this.getKUnitByName(toUnitName);
const eFromUnit = this.getKUnitByName(drawingUnit);
this.items.forEach((item) => {
if (toUnitName === "Default") {
item.setUnit(renameUnit(this.renameUnitTable, drawingUnit));
item.setConversionFactor(1.0);
}
else {
item.setUnit(renameUnit(this.renameUnitTable, toUnitName));
const multiplier = this.m_module.getViewer().getUnitsConversionCoef(eFromUnit, eToUnit);
this.conversionFactor = 1 / multiplier;
item.setConversionFactor(this.conversionFactor);
}
});
}
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 "Me