Initial commit

This commit is contained in:
Viet An
2026-03-02 09:45:33 +07:00
commit d17a9e2588
415 changed files with 92113 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
export const utopiaUrn = 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6cGtxYjhrbHBnZWtsZ2tlbHBzanBoYzljMm5neXhtbjY0cXZocHNhcXVodjQ2emVuLWJhc2ljLWFwcC8yNi4wMS4xNiUyMC0lMjBFeHBvcnQlMjBUTUIlMjAtJTIwUGhhbiUyMG1lbS5kd2c';
export const blankUrn = 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6cGtxYjhrbHBnZWtsZ2tlbHBzanBoYzljMm5neXhtbjY0cXZocHNhcXVodjQ2emVuLWJhc2ljLWFwcC9ibGFuay5kd2c';
export async function getAccessToken(callback) {
try {
const { access_token, expires_in } = await $fetch('/api/apsAuthToken');
callback(access_token, expires_in);
} catch (err) {
console.error('Could not obtain access token. Error:', err);
}
}
export function loadModel(viewer, urn, xform) {
return new Promise((resolve, reject) => {
function onDocumentLoadSuccess(doc) {
const viewable = doc.getRoot().getDefaultGeometry();
const options = {
keepCurrentModels: true,
};
if (xform) {
options.placementTransform = xform;
}
viewer.loadDocumentNode(doc, viewable, options).then(resolve).catch(reject);
}
function onDocumentLoadFailure(code, message, errors) {
reject({ code, message, errors });
}
viewer.setLightPreset(0);
Autodesk.Viewing.Document.load(`urn:${urn}`, onDocumentLoadSuccess, onDocumentLoadFailure);
});
}
function showNotification(message) {
const overlay = document.getElementById('overlay');
overlay.innerHTML = `<div class="notification">${message}</div>`;
overlay.style.display = 'flex';
}
function clearNotification() {
const overlay = document.getElementById('overlay');
overlay.innerHTML = '';
overlay.style.display = 'none';
}
export async function setupModelSelection(viewer) {
if (window.onModelSelectedTimeout) {
clearTimeout(window.onModelSelectedTimeout);
delete window.onModelSelectedTimeout;
}
try {
const res = await $fetch(`/api/models/${utopiaUrn}/status`);
const { status } = res;
switch (status.status) {
case 'n/a':
showNotification(`Model has not been translated.`);
break;
case 'inprogress':
showNotification(`Model is being translated (${status.progress})...`);
window.onModelSelectedTimeout = setTimeout(onModelSelected, 5000, viewer, utopiaUrn);
break;
case 'failed':
showNotification(
`Translation failed. <ul>${status.messages.map((msg) => `<li>${JSON.stringify(msg)}</li>`).join('')}</ul>`,
);
break;
default:
clearNotification();
loadModel(viewer, utopiaUrn);
break;
}
} catch (err) {
console.error('Could not load model. Error:', err);
}
}

View File

@@ -0,0 +1,112 @@
import { blankUrn, utopiaUrn } from '@/components/viewer/utils/aps-init';
import { isNotNil } from 'es-toolkit';
function isTemInsideLand(landBounds, temBounds) {
const tolerance = 0.007;
return (
landBounds.min.x - temBounds.min.x <= tolerance
&& landBounds.min.y - temBounds.min.y <= tolerance
&& landBounds.max.x - temBounds.max.x >= -tolerance
&& landBounds.max.y - temBounds.max.y >= -tolerance
);
}
export function findTemInside(land, tems) {
const temInside = tems.find(tem => {
const temBounds = tem.bounds[0];
for (const landBounds of land.bounds) {
if (isTemInsideLand(landBounds, temBounds)) return true;
}
});
return temInside;
}
export const getTradeCodeFromTem = (tem) => tem.properties.find(prop => prop.displayName === 'LO').displayValue;
export const objIsTem = (obj) => obj.name && obj.name.startsWith('Blk003');
export const objIsLand = (obj) => {
const layerProp = obj.properties.find((prop) => prop.displayName === 'Layer');
if (!layerProp) return false;
const globalWidthProp = obj.properties.find((prop) => prop.displayName === 'Global width');
return (
layerProp.displayValue === '1-bodim' ||
layerProp.displayValue === '0' /* special case - Z.E02.02A */
) && globalWidthProp.displayValue === 0;
}
export function findLandByTradeCode(trade_code, lands, temsInside) {
const foundTemIndex = temsInside.findIndex(tem => tem && getTradeCodeFromTem(tem) === trade_code);
return foundTemIndex >= 0 ? [lands[foundTemIndex], foundTemIndex] : undefined;
}
export function pan(viewer) {
const navigation = viewer.navigation;
const position = navigation.getPosition();
const target = navigation.getTarget();
// offset both target and position to maintain angle
const panOffset = new THREE.Vector3(2, 0, 0);
navigation.setPosition(position.clone().add(panOffset));
navigation.setTarget(target.clone().add(panOffset));
}
export function unloadUnusedExtensions(viewer) {
viewer.addEventListener(Autodesk.Viewing.EXTENSION_LOADED_EVENT, (e) => {
if (
['Autodesk.Measure', 'Autodesk.DocumentBrowser', 'Autodesk.DefaultTools.NavTools'].includes(e.extensionId)
) {
viewer.unloadExtension(e.extensionId);
}
});
}
export function addTemSelectionListener(viewer, products, openProductViewModal, openNoPermissionModal) {
viewer.addEventListener(Autodesk.Viewing.AGGREGATE_SELECTION_CHANGED_EVENT, (e) => {
viewer.clearSelection();
if (e.selections.length !== 1) return;
const [selectedObj] = e.selections;
const { model, dbIdArray } = selectedObj;
if (dbIdArray.length !== 1) return;
if (model.loader.svfUrn === utopiaUrn) {
const [dbId] = dbIdArray;
viewer.getProperties(dbId, (obj) => {
if (!objIsTem(obj)) return;
const trade_code = getTradeCodeFromTem(obj);
const product = products.find(p => p.trade_code === trade_code);
product ? openProductViewModal(product) : openNoPermissionModal();
});
} else if (model.loader.svf.isSceneBuilder) {
const [dbId] = dbIdArray;
const product = products.find(p => p.id === dbId);
product ? openProductViewModal(product) : openNoPermissionModal();
} else if (model.loader.svfUrn === blankUrn) {
viewer.clearSelection(); // make unselectable
}
},
(err) => console.error(err),
);
}
export function getLayerNames() {
const { viewer } = window;
if (!viewer) return;
if (!viewer.impl) return;
const layerNames = viewer.impl.layers.indexToLayer
.filter(obj => isNotNil(obj)) // not counting root
.filter(obj => obj.visible)
.map(({ layer }) => layer.name)
.sort((a, b) => a.localeCompare(b));
return layerNames;
}
export function applyLayerSetting(layersetting, store) {
const { viewer } = window;
viewer.setLayerVisible(null, false); // first, hide everything
viewer.setLayerVisible(layersetting?.detail || null, true); // show specific ones, or all if there's no setting
store.commit('layersetting', layersetting);
}

View File

@@ -0,0 +1,37 @@
export async function renderSvg(relativePath) {
const svgRes = await fetch(relativePath);
const svgText = await svgRes.text();
const parser = new DOMParser();
const svgDoc = parser.parseFromString(svgText, 'image/svg+xml');
const svgElement = svgDoc.documentElement;
return svgElement;
}
export function html(tag, props = {}, children = []) {
const element = document.createElement(tag);
Object.entries(props).forEach(([key, value]) => {
if (key === 'textContent' || key === 'innerHTML') {
element[key] = value;
} else if (key === 'style' && typeof value === 'object') {
Object.assign(element.style, value);
} else if (key === 'class') {
element.className = value;
} else if (key.startsWith('on')) {
element.addEventListener(key.slice(2).toLowerCase(), value);
} else {
element.setAttribute(key, value);
}
});
children.flat().forEach(child => {
if (typeof child === 'string') {
element.appendChild(document.createTextNode(child));
} else if (child instanceof Node) {
element.appendChild(child);
}
});
return element;
}

View File

@@ -0,0 +1,100 @@
export class GeometryCallback {
constructor(viewer, vpXform) {
this.viewer = viewer;
this.vpXform = vpXform;
this.lines = [];
}
onLineSegment(x1, y1, x2, y2) {
let pt1 = new THREE.Vector3().set(x1, y1, 0).applyMatrix4(this.vpXform);
let pt2 = new THREE.Vector3().set(x2, y2, 0).applyMatrix4(this.vpXform);
this.lines.push({
x1: pt1.x,
y1: pt1.y,
x2: pt2.x,
y2: pt2.y
});
}
}
export function getBounds(dbId, frags) {
let fragIds = frags.fragments.dbId2fragId[dbId];
if (typeof fragIds === 'number') {
fragIds = [fragIds];
};
const bounds = fragIds.map(fId => {
const bound = new THREE.Box3();
const boundsCallback = new Autodesk.Viewing.Private.BoundsCallback(bound);
const mesh = frags.getVizmesh(fId);
const vbr = new Autodesk.Viewing.Private.VertexBufferReader(mesh.geometry, viewer.impl.use2dInstancing);
vbr.enumGeomsForObject(dbId, boundsCallback);
return bound;
});
return bounds;
}
export function extractPoints(lines) {
const tolerance = 0.001;
const allPoints = [];
for (let i = 0; i < lines.length; i++) {
const { x1, y1, x2, y2 } = lines[i];
allPoints.push({ x: x1, y: y1 });
allPoints.push({ x: x2, y: y2 });
}
const pointsDeduped = [];
for (let i = 0; i < allPoints.length; i++) {
const element = allPoints[i];
const notFound = !pointsDeduped.find(pt => Math.abs(pt.x - element.x) < tolerance && Math.abs(pt.y - element.y) < tolerance);
if (notFound) pointsDeduped.push(element);
}
return pointsDeduped;
}
export function sortByAngle(points) {
// Calculate centroid
const centroid = new THREE.Vector3();
points.forEach(v => centroid.add(v));
centroid.divideScalar(points.length);
// Sort by angle around centroid
return points.slice().sort((a, b) => {
const angleA = Math.atan2(a.y - centroid.y, a.x - centroid.x);
const angleB = Math.atan2(b.y - centroid.y, b.x - centroid.x);
return angleA - angleB;
});
}
export function getColorByIndex(i) {
const colors = [
'magenta',
'red',
'orange',
'yellow',
'chartreuse',
'green',
'blue',
]
if (i === 0) return 'black';
return colors[i % 7];
}
/**
* - THREE.Color: `{ r: number, g: number, b: number }`
* - THREE.Vector4: `{ x: number, y: number, z: number, w: number }`
*
* @param {string} colorStr e.g. `'#ff0000'`, `'white'`, `'hotpink'`
* @returns A Vector4 object
*/
export function colorStrToVector4(colorStr) {
const color = new THREE.Color().setStyle(colorStr);
const vector4 = new THREE.Vector4(color.r, color.g, color.b, 1);
return vector4;
}