mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
chore(deps): update dependency @vue/tsconfig to ^0.8.0 (#251)
* chore(deps): update dependency @vue/tsconfig to ^0.8.0 * Fix new ts issues * Add null checks for selection and model objects throughout frontend This improves robustness by handling cases where selection or model objects may be missing or undefined, preventing runtime errors. --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Yeicor <4929005+yeicor@users.noreply.github.com>
This commit is contained in:
@@ -51,6 +51,7 @@ async function onModelUpdateRequest(event: NetworkUpdateEvent) {
|
|||||||
for (let modelIndex in event.models) {
|
for (let modelIndex in event.models) {
|
||||||
let isLast = parseInt(modelIndex) === event.models.length - 1;
|
let isLast = parseInt(modelIndex) === event.models.length - 1;
|
||||||
let model = event.models[modelIndex];
|
let model = event.models[modelIndex];
|
||||||
|
if (!model) continue;
|
||||||
tools.value?.removeObjectSelections(model.name);
|
tools.value?.removeObjectSelections(model.name);
|
||||||
try {
|
try {
|
||||||
let loadHelpers = (await settings).loadHelpers;
|
let loadHelpers = (await settings).loadHelpers;
|
||||||
@@ -153,7 +154,9 @@ document.body.addEventListener("drop", async e => {
|
|||||||
<span v-if="preloadingModels.length > 0" class="d-block text-center my-16">
|
<span v-if="preloadingModels.length > 0" class="d-block text-center my-16">
|
||||||
<span class="d-block text-center text-h6">Still trying to load the following:</span>
|
<span class="d-block text-center text-h6">Still trying to load the following:</span>
|
||||||
<span class="d-block text-center" v-for="(model, index) in preloadingModels" :key="index">
|
<span class="d-block text-center" v-for="(model, index) in preloadingModels" :key="index">
|
||||||
|
<template v-if="model !== undefined">
|
||||||
{{ model }}<span v-if="index < preloadingModels.length - 1">, </span>
|
{{ model }}<span v-if="index < preloadingModels.length - 1">, </span>
|
||||||
|
</template>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import {BufferAttribute, InterleavedBufferAttribute, Vector3} from 'three';
|
import { BufferAttribute, InterleavedBufferAttribute, Vector3 } from "three";
|
||||||
import type {MObject3D} from "../tools/Selection.vue";
|
import type { MObject3D } from "../tools/Selection.vue";
|
||||||
import type {ModelScene} from '@google/model-viewer/lib/three-components/ModelScene';
|
import type { ModelScene } from "@google/model-viewer/lib/three-components/ModelScene";
|
||||||
import type {SelectionInfo} from "../tools/selection";
|
import type { SelectionInfo } from "../tools/selection";
|
||||||
|
|
||||||
|
function getCenterAndVertexList(
|
||||||
function getCenterAndVertexList(selInfo: SelectionInfo, scene: ModelScene): {
|
selInfo: SelectionInfo,
|
||||||
center: Vector3,
|
scene: ModelScene,
|
||||||
vertices: Array<Vector3>
|
): {
|
||||||
|
center: Vector3;
|
||||||
|
vertices: Array<Vector3>;
|
||||||
} {
|
} {
|
||||||
let pos: BufferAttribute | InterleavedBufferAttribute = selInfo.object.geometry.getAttribute('position');
|
if (!selInfo.object?.geometry) {
|
||||||
let ind: BufferAttribute | null = selInfo.object.geometry.index;
|
throw new Error("selInfo.object or geometry is undefined");
|
||||||
|
}
|
||||||
|
let pos = selInfo.object.geometry.getAttribute("position");
|
||||||
|
let ind = selInfo.object.geometry.index;
|
||||||
if (ind === null) {
|
if (ind === null) {
|
||||||
ind = new BufferAttribute(new Uint16Array(pos.count), 1);
|
ind = new BufferAttribute(new Uint16Array(pos.count), 1);
|
||||||
for (let i = 0; i < pos.count; i++) {
|
for (let i = 0; i < pos.count; i++) {
|
||||||
@@ -19,29 +24,33 @@ function getCenterAndVertexList(selInfo: SelectionInfo, scene: ModelScene): {
|
|||||||
let center = new Vector3();
|
let center = new Vector3();
|
||||||
let vertices = [];
|
let vertices = [];
|
||||||
for (let i = selInfo.indices[0]; i < selInfo.indices[1]; i++) {
|
for (let i = selInfo.indices[0]; i < selInfo.indices[1]; i++) {
|
||||||
let index = ind.getX(i)
|
let index = ind.getX(i);
|
||||||
let vertex = new Vector3(pos.getX(index), pos.getY(index), pos.getZ(index));
|
let vertex = new Vector3(pos.getX(index), pos.getY(index), pos.getZ(index));
|
||||||
vertex = scene.target.worldToLocal(selInfo.object.localToWorld(vertex));
|
vertex = scene.target.worldToLocal(selInfo.object.localToWorld(vertex));
|
||||||
center.add(vertex);
|
center.add(vertex);
|
||||||
vertices.push(vertex);
|
vertices.push(vertex);
|
||||||
}
|
}
|
||||||
center = center.divideScalar(selInfo.indices[1] - selInfo.indices[0]);
|
center = center.divideScalar(selInfo.indices[1] - selInfo.indices[0]);
|
||||||
return {center, vertices};
|
return { center, vertices };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given two THREE.Object3D objects, returns their closest and farthest vertices, and the geometric centers.
|
* Given two THREE.Object3D objects, returns their closest and farthest vertices, and the geometric centers.
|
||||||
* All of them are approximated and should not be used for precise calculations.
|
* All of them are approximated and should not be used for precise calculations.
|
||||||
*/
|
*/
|
||||||
export function distances(a: SelectionInfo, b: SelectionInfo, scene: ModelScene): {
|
export function distances(
|
||||||
min: Array<Vector3>,
|
a: SelectionInfo,
|
||||||
center: Array<Vector3>,
|
b: SelectionInfo,
|
||||||
max: Array<Vector3>
|
scene: ModelScene,
|
||||||
|
): {
|
||||||
|
min: Array<Vector3>;
|
||||||
|
center: Array<Vector3>;
|
||||||
|
max: Array<Vector3>;
|
||||||
} {
|
} {
|
||||||
// Simplify this problem (approximate) by using the distance between each of their vertices.
|
// Simplify this problem (approximate) by using the distance between each of their vertices.
|
||||||
// Find the center of each object.
|
// Find the center of each object.
|
||||||
let {center: aCenter, vertices: aVertices} = getCenterAndVertexList(a, scene);
|
let { center: aCenter, vertices: aVertices } = getCenterAndVertexList(a, scene);
|
||||||
let {center: bCenter, vertices: bVertices} = getCenterAndVertexList(b, scene);
|
let { center: bCenter, vertices: bVertices } = getCenterAndVertexList(b, scene);
|
||||||
|
|
||||||
// Find the closest and farthest vertices.
|
// Find the closest and farthest vertices.
|
||||||
// TODO: Compute actual min and max distances between the two objects.
|
// TODO: Compute actual min and max distances between the two objects.
|
||||||
@@ -52,16 +61,20 @@ export function distances(a: SelectionInfo, b: SelectionInfo, scene: ModelScene)
|
|||||||
let maxDistanceVertices = [new Vector3(), new Vector3()];
|
let maxDistanceVertices = [new Vector3(), new Vector3()];
|
||||||
for (let i = 0; i < aVertices.length; i++) {
|
for (let i = 0; i < aVertices.length; i++) {
|
||||||
for (let j = 0; j < bVertices.length; j++) {
|
for (let j = 0; j < bVertices.length; j++) {
|
||||||
let distance = aVertices[i].distanceTo(bVertices[j]);
|
const aVertex = aVertices[i];
|
||||||
|
const bVertex = bVertices[j];
|
||||||
|
if (aVertex && bVertex) {
|
||||||
|
let distance = aVertex.distanceTo(bVertex);
|
||||||
if (distance < minDistance) {
|
if (distance < minDistance) {
|
||||||
minDistance = distance;
|
minDistance = distance;
|
||||||
minDistanceVertices[0] = aVertices[i];
|
minDistanceVertices[0] = aVertex;
|
||||||
minDistanceVertices[1] = bVertices[j];
|
minDistanceVertices[1] = bVertex;
|
||||||
}
|
}
|
||||||
if (distance > maxDistance) {
|
if (distance > maxDistance) {
|
||||||
maxDistance = distance;
|
maxDistance = distance;
|
||||||
maxDistanceVertices[0] = aVertices[i];
|
maxDistanceVertices[0] = aVertex;
|
||||||
maxDistanceVertices[1] = bVertices[j];
|
maxDistanceVertices[1] = bVertex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,6 +83,6 @@ export function distances(a: SelectionInfo, b: SelectionInfo, scene: ModelScene)
|
|||||||
return {
|
return {
|
||||||
min: minDistanceVertices,
|
min: minDistanceVertices,
|
||||||
center: [aCenter, bCenter],
|
center: [aCenter, bCenter],
|
||||||
max: maxDistanceVertices
|
max: maxDistanceVertices,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import {Buffer, Document, Scene, type Transform, WebIO} from "@gltf-transform/core";
|
import { Buffer, Document, Scene, type Transform, WebIO } from "@gltf-transform/core";
|
||||||
import {mergeDocuments, unpartition} from "@gltf-transform/functions";
|
import { mergeDocuments, unpartition } from "@gltf-transform/functions";
|
||||||
import {retrieveFile} from "../tools/upload-file.ts";
|
import { retrieveFile } from "../tools/upload-file.ts";
|
||||||
|
|
||||||
let io = new WebIO();
|
let io = new WebIO();
|
||||||
export let extrasNameKey = "__yacv_name";
|
export let extrasNameKey = "__yacv_name";
|
||||||
export let extrasNameValueHelpers = "__helpers";
|
export let extrasNameValueHelpers = "__helpers";
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
let isSmallBuild = typeof __YACV_SMALL_BUILD__ !== 'undefined' && __YACV_SMALL_BUILD__;
|
let isSmallBuild = typeof __YACV_SMALL_BUILD__ !== "undefined" && __YACV_SMALL_BUILD__;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a GLB model from a URL and adds it to the document or replaces it if the names match.
|
* Loads a GLB model from a URL and adds it to the document or replaces it if the names match.
|
||||||
@@ -16,8 +16,12 @@ let isSmallBuild = typeof __YACV_SMALL_BUILD__ !== 'undefined' && __YACV_SMALL_B
|
|||||||
*
|
*
|
||||||
* Remember to call mergeFinalize after all models have been merged (slower required operations).
|
* Remember to call mergeFinalize after all models have been merged (slower required operations).
|
||||||
*/
|
*/
|
||||||
export async function mergePartial(url: string | Blob, name: string, document: Document, networkFinished: () => void = () => {
|
export async function mergePartial(
|
||||||
}): Promise<Document> {
|
url: string | Blob,
|
||||||
|
name: string,
|
||||||
|
document: Document,
|
||||||
|
networkFinished: () => void = () => {},
|
||||||
|
): Promise<Document> {
|
||||||
// Fetch the complete document from the network
|
// Fetch the complete document from the network
|
||||||
// This could be done at the same time as the document is being processed, but I wanted better metrics
|
// This could be done at the same time as the document is being processed, but I wanted better metrics
|
||||||
let response = await fetchOrRead(url);
|
let response = await fetchOrRead(url);
|
||||||
@@ -26,29 +30,34 @@ export async function mergePartial(url: string | Blob, name: string, document: D
|
|||||||
|
|
||||||
// Load the new document
|
// Load the new document
|
||||||
let newDoc = null;
|
let newDoc = null;
|
||||||
let alreadyTried: { [name: string]: boolean } = {}
|
let alreadyTried: { [name: string]: boolean } = {};
|
||||||
while (newDoc == null) { // Retry adding extensions as required until the document is loaded
|
while (newDoc == null) {
|
||||||
try { // Try to load fast if no extensions are used
|
// Retry adding extensions as required until the document is loaded
|
||||||
|
try {
|
||||||
|
// Try to load fast if no extensions are used
|
||||||
newDoc = await io.readBinary(new Uint8Array(buffer));
|
newDoc = await io.readBinary(new Uint8Array(buffer));
|
||||||
} catch (e) { // Fallback to wait for download and register big extensions
|
} catch (e) {
|
||||||
|
// Fallback to wait for download and register big extensions
|
||||||
if (!isSmallBuild && e instanceof Error && e.message.toLowerCase().includes("khr_draco_mesh_compression")) {
|
if (!isSmallBuild && e instanceof Error && e.message.toLowerCase().includes("khr_draco_mesh_compression")) {
|
||||||
if (alreadyTried["draco"]) throw e; else alreadyTried["draco"] = true;
|
if (alreadyTried["draco"]) throw e;
|
||||||
|
else alreadyTried["draco"] = true;
|
||||||
// WARNING: Draco decompression on web is really slow for non-trivial models! (it should work?)
|
// WARNING: Draco decompression on web is really slow for non-trivial models! (it should work?)
|
||||||
let {KHRDracoMeshCompression} = await import("@gltf-transform/extensions")
|
let { KHRDracoMeshCompression } = await import("@gltf-transform/extensions");
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
let dracoDecoderWeb = await import("three/examples/jsm/libs/draco/draco_decoder.js");
|
let dracoDecoderWeb = await import("three/examples/jsm/libs/draco/draco_decoder.js");
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
let dracoEncoderWeb = await import("three/examples/jsm/libs/draco/draco_encoder.js");
|
let dracoEncoderWeb = await import("three/examples/jsm/libs/draco/draco_encoder.js");
|
||||||
io.registerExtensions([KHRDracoMeshCompression])
|
io.registerExtensions([KHRDracoMeshCompression]).registerDependencies({
|
||||||
.registerDependencies({
|
"draco3d.decoder": await dracoDecoderWeb.default({}),
|
||||||
'draco3d.decoder': await dracoDecoderWeb.default({}),
|
"draco3d.encoder": await dracoEncoderWeb.default({}),
|
||||||
'draco3d.encoder': await dracoEncoderWeb.default({})
|
|
||||||
});
|
});
|
||||||
} else if (!isSmallBuild && e instanceof Error && e.message.toLowerCase().includes("ext_texture_webp")) {
|
} else if (!isSmallBuild && e instanceof Error && e.message.toLowerCase().includes("ext_texture_webp")) {
|
||||||
if (alreadyTried["webp"]) throw e; else alreadyTried["webp"] = true;
|
if (alreadyTried["webp"]) throw e;
|
||||||
let {EXTTextureWebP} = await import("@gltf-transform/extensions")
|
else alreadyTried["webp"] = true;
|
||||||
|
let { EXTTextureWebP } = await import("@gltf-transform/extensions");
|
||||||
io.registerExtensions([EXTTextureWebP]);
|
io.registerExtensions([EXTTextureWebP]);
|
||||||
} else { // TODO: Add more extensions as required
|
} else {
|
||||||
|
// TODO: Add more extensions as required
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,18 +92,24 @@ export async function removeModel(name: string, document: Document): Promise<Doc
|
|||||||
function setNames(name: string): Transform {
|
function setNames(name: string): Transform {
|
||||||
return (doc: Document) => {
|
return (doc: Document) => {
|
||||||
// Do this automatically for all elements changing any name
|
// Do this automatically for all elements changing any name
|
||||||
for (let elem of doc.getGraph().listEdges().map(e => e.getChild())) {
|
for (let elem of doc
|
||||||
|
.getGraph()
|
||||||
|
.listEdges()
|
||||||
|
.map((e) => e.getChild())) {
|
||||||
if (!elem.getExtras()) elem.setExtras({});
|
if (!elem.getExtras()) elem.setExtras({});
|
||||||
elem.getExtras()[extrasNameKey] = name;
|
elem.getExtras()[extrasNameKey] = name;
|
||||||
}
|
}
|
||||||
return doc;
|
return doc;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Ensures that all elements with the given name are removed from the document */
|
/** Ensures that all elements with the given name are removed from the document */
|
||||||
function dropByName(name: string): Transform {
|
function dropByName(name: string): Transform {
|
||||||
return (doc: Document) => {
|
return (doc: Document) => {
|
||||||
for (let elem of doc.getGraph().listEdges().map(e => e.getChild())) {
|
for (let elem of doc
|
||||||
|
.getGraph()
|
||||||
|
.listEdges()
|
||||||
|
.map((e) => e.getChild())) {
|
||||||
if (elem.getExtras() == null || elem instanceof Scene || elem instanceof Buffer) continue;
|
if (elem.getExtras() == null || elem instanceof Scene || elem instanceof Buffer) continue;
|
||||||
if ((elem.getExtras()[extrasNameKey]?.toString() ?? "") == name) {
|
if ((elem.getExtras()[extrasNameKey]?.toString() ?? "") == name) {
|
||||||
elem.dispose();
|
elem.dispose();
|
||||||
@@ -104,12 +119,14 @@ function dropByName(name: string): Transform {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Merges all scenes in the document into a single default scene */
|
/** Merges all scenes in the document into a single default scene */
|
||||||
function mergeScenes(): Transform {
|
function mergeScenes(): Transform {
|
||||||
return (doc: Document) => {
|
return (doc: Document) => {
|
||||||
let root = doc.getRoot();
|
let root = doc.getRoot();
|
||||||
let scene = root.getDefaultScene() ?? root.listScenes()[0];
|
let scene = root.getDefaultScene() ?? root.listScenes()[0];
|
||||||
|
if (!scene) {
|
||||||
|
throw new Error("No scene found in GLTF document");
|
||||||
|
}
|
||||||
for (let dropScene of root.listScenes()) {
|
for (let dropScene of root.listScenes()) {
|
||||||
if (dropScene === scene) continue;
|
if (dropScene === scene) continue;
|
||||||
for (let node of dropScene.listChildren()) {
|
for (let node of dropScene.listChildren()) {
|
||||||
@@ -117,7 +134,7 @@ function mergeScenes(): Transform {
|
|||||||
}
|
}
|
||||||
dropScene.dispose();
|
dropScene.dispose();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fetches a URL or reads it if it is a Blob URL */
|
/** Fetches a URL or reads it if it is a Blob URL */
|
||||||
@@ -143,6 +160,4 @@ async function fetchOrRead(url: string | Blob) {
|
|||||||
// Fetch the URL
|
// Fetch the URL
|
||||||
return retrieveFile(url);
|
return retrieveFile(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,54 +1,87 @@
|
|||||||
// noinspection JSVoidFunctionReturnValueUsed,JSUnresolvedReference
|
// noinspection JSVoidFunctionReturnValueUsed,JSUnresolvedReference
|
||||||
|
|
||||||
import {Document, type TypedArray} from '@gltf-transform/core'
|
import { Document, type TypedArray } from "@gltf-transform/core";
|
||||||
import {Vector2} from "three/src/math/Vector2.js"
|
import { Vector2 } from "three/src/math/Vector2.js";
|
||||||
import {Vector3} from "three/src/math/Vector3.js"
|
import { Vector3 } from "three/src/math/Vector3.js";
|
||||||
import {Matrix4} from "three/src/math/Matrix4.js"
|
import { Matrix4 } from "three/src/math/Matrix4.js";
|
||||||
|
|
||||||
|
|
||||||
/** Exports the colors used for the axes, primary and secondary. They match the orientation gizmo. */
|
/** Exports the colors used for the axes, primary and secondary. They match the orientation gizmo. */
|
||||||
export const AxesColors = {
|
export const AxesColors = {
|
||||||
x: [[247, 60, 60], [148, 36, 36]],
|
x: [
|
||||||
z: [[108, 203, 38], [65, 122, 23]],
|
[247, 60, 60],
|
||||||
y: [[23, 140, 240], [14, 84, 144]]
|
[148, 36, 36],
|
||||||
}
|
],
|
||||||
|
z: [
|
||||||
|
[108, 203, 38],
|
||||||
|
[65, 122, 23],
|
||||||
|
],
|
||||||
|
y: [
|
||||||
|
[23, 140, 240],
|
||||||
|
[14, 84, 144],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
function buildSimpleGltf(doc: Document, rawPositions: number[], rawIndices: number[], rawColors: number[] | null, transform: Matrix4, name: string = '__helper', mode: number = WebGL2RenderingContext.LINES) {
|
function buildSimpleGltf(
|
||||||
const buffer = doc.getRoot().listBuffers()[0] ?? doc.createBuffer(name + 'Buffer')
|
doc: Document,
|
||||||
const scene = doc.getRoot().getDefaultScene() ?? doc.getRoot().listScenes()[0] ?? doc.createScene(name + 'Scene')
|
rawPositions: number[],
|
||||||
const positions = doc.createAccessor(name + 'Position')
|
rawIndices: number[],
|
||||||
|
rawColors: number[] | null,
|
||||||
|
transform: Matrix4,
|
||||||
|
name: string = "__helper",
|
||||||
|
mode: number = WebGL2RenderingContext.LINES,
|
||||||
|
) {
|
||||||
|
const buffer = doc.getRoot().listBuffers()[0] ?? doc.createBuffer(name + "Buffer");
|
||||||
|
const scene = doc.getRoot().getDefaultScene() ?? doc.getRoot().listScenes()[0] ?? doc.createScene(name + "Scene");
|
||||||
|
if (!scene) throw new Error("Scene is undefined");
|
||||||
|
if (!rawPositions) throw new Error("rawPositions is undefined");
|
||||||
|
const positions = doc
|
||||||
|
.createAccessor(name + "Position")
|
||||||
.setArray(new Float32Array(rawPositions) as TypedArray)
|
.setArray(new Float32Array(rawPositions) as TypedArray)
|
||||||
.setType('VEC3')
|
.setType("VEC3")
|
||||||
.setBuffer(buffer)
|
.setBuffer(buffer);
|
||||||
const indices = doc.createAccessor(name + 'Indices')
|
const indices = doc
|
||||||
|
.createAccessor(name + "Indices")
|
||||||
.setArray(new Uint32Array(rawIndices) as TypedArray)
|
.setArray(new Uint32Array(rawIndices) as TypedArray)
|
||||||
.setType('SCALAR')
|
.setType("SCALAR")
|
||||||
.setBuffer(buffer)
|
.setBuffer(buffer);
|
||||||
let colors = null;
|
let colors = null;
|
||||||
if (rawColors) {
|
if (rawColors) {
|
||||||
colors = doc.createAccessor(name + 'Color')
|
colors = doc
|
||||||
|
.createAccessor(name + "Color")
|
||||||
.setArray(new Float32Array(rawColors) as TypedArray)
|
.setArray(new Float32Array(rawColors) as TypedArray)
|
||||||
.setType('VEC4')
|
.setType("VEC4")
|
||||||
.setBuffer(buffer);
|
.setBuffer(buffer);
|
||||||
}
|
}
|
||||||
const material = doc.createMaterial(name + 'Material')
|
const material = doc.createMaterial(name + "Material").setAlphaMode("OPAQUE");
|
||||||
.setAlphaMode('OPAQUE')
|
const geometry = doc
|
||||||
const geometry = doc.createPrimitive()
|
.createPrimitive()
|
||||||
.setIndices(indices)
|
.setIndices(indices)
|
||||||
.setAttribute('POSITION', positions)
|
.setAttribute("POSITION", positions)
|
||||||
.setMode(mode as any)
|
.setMode(mode as any)
|
||||||
.setMaterial(material)
|
.setMaterial(material);
|
||||||
if (rawColors) {
|
if (rawColors) {
|
||||||
geometry.setAttribute('COLOR_0', colors)
|
geometry.setAttribute("COLOR_0", colors);
|
||||||
}
|
}
|
||||||
if (mode == WebGL2RenderingContext.TRIANGLES) {
|
if (mode == WebGL2RenderingContext.TRIANGLES) {
|
||||||
geometry.setExtras({face_triangles_end: [rawIndices.length / 6, rawIndices.length * 2 / 6, rawIndices.length * 3 / 6, rawIndices.length * 4 / 6, rawIndices.length * 5 / 6, rawIndices.length]})
|
geometry.setExtras({
|
||||||
|
face_triangles_end: [
|
||||||
|
rawIndices.length / 6,
|
||||||
|
(rawIndices.length * 2) / 6,
|
||||||
|
(rawIndices.length * 3) / 6,
|
||||||
|
(rawIndices.length * 4) / 6,
|
||||||
|
(rawIndices.length * 5) / 6,
|
||||||
|
rawIndices.length,
|
||||||
|
],
|
||||||
|
});
|
||||||
} else if (mode == WebGL2RenderingContext.LINES) {
|
} else if (mode == WebGL2RenderingContext.LINES) {
|
||||||
geometry.setExtras({edge_points_end: [rawIndices.length / 3, rawIndices.length * 2 / 3, rawIndices.length]})
|
geometry.setExtras({ edge_points_end: [rawIndices.length / 3, (rawIndices.length * 2) / 3, rawIndices.length] });
|
||||||
}
|
}
|
||||||
const mesh = doc.createMesh(name + 'Mesh').addPrimitive(geometry)
|
const mesh = doc.createMesh(name + "Mesh").addPrimitive(geometry);
|
||||||
const node = doc.createNode(name + 'Node').setMesh(mesh).setMatrix(transform.elements as any)
|
const node = doc
|
||||||
scene.addChild(node)
|
.createNode(name + "Node")
|
||||||
|
.setMesh(mesh)
|
||||||
|
.setMatrix(transform.elements as any);
|
||||||
|
scene.addChild(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,23 +89,36 @@ function buildSimpleGltf(doc: Document, rawPositions: number[], rawIndices: numb
|
|||||||
*/
|
*/
|
||||||
export function newAxes(doc: Document, size: Vector3, transform: Matrix4) {
|
export function newAxes(doc: Document, size: Vector3, transform: Matrix4) {
|
||||||
let rawIndices = [0, 1, 2, 3, 4, 5];
|
let rawIndices = [0, 1, 2, 3, 4, 5];
|
||||||
let rawPositions = [
|
let rawPositions = [0, 0, 0, size.x, 0, 0, 0, 0, 0, 0, size.y, 0, 0, 0, 0, 0, 0, -size.z];
|
||||||
0, 0, 0, size.x, 0, 0,
|
|
||||||
0, 0, 0, 0, size.y, 0,
|
|
||||||
0, 0, 0, 0, 0, -size.z,
|
|
||||||
];
|
|
||||||
let rawColors = [
|
let rawColors = [
|
||||||
...(AxesColors.x[0]), 255, ...(AxesColors.x[1]), 255,
|
...(AxesColors.x[0] ?? [255, 0, 0]),
|
||||||
...(AxesColors.y[0]), 255, ...(AxesColors.y[1]), 255,
|
255,
|
||||||
...(AxesColors.z[0]), 255, ...(AxesColors.z[1]), 255
|
...(AxesColors.x[1] ?? [255, 0, 0]),
|
||||||
].map(x => x / 255.0);
|
255,
|
||||||
|
...(AxesColors.y[0] ?? [0, 255, 0]),
|
||||||
|
255,
|
||||||
|
...(AxesColors.y[1] ?? [0, 255, 0]),
|
||||||
|
255,
|
||||||
|
...(AxesColors.z[0] ?? [0, 0, 255]),
|
||||||
|
255,
|
||||||
|
...(AxesColors.z[1] ?? [0, 0, 255]),
|
||||||
|
255,
|
||||||
|
].map((x) => x / 255.0);
|
||||||
// Axes at (0, 0, 0)
|
// Axes at (0, 0, 0)
|
||||||
buildSimpleGltf(doc, rawPositions, rawIndices, rawColors, new Matrix4(), '__helper_axes');
|
buildSimpleGltf(doc, rawPositions, rawIndices, rawColors, new Matrix4(), "__helper_axes");
|
||||||
buildSimpleGltf(doc, [0, 0, 0], [0], [1, 1, 1, 1], new Matrix4(), '__helper_axes', WebGL2RenderingContext.POINTS);
|
buildSimpleGltf(doc, [0, 0, 0], [0], [1, 1, 1, 1], new Matrix4(), "__helper_axes", WebGL2RenderingContext.POINTS);
|
||||||
// Axes at center
|
// Axes at center
|
||||||
if (new Matrix4() != transform) {
|
if (new Matrix4() != transform) {
|
||||||
buildSimpleGltf(doc, rawPositions, rawIndices, rawColors, transform, '__helper_axes_center');
|
buildSimpleGltf(doc, rawPositions, rawIndices, rawColors, transform, "__helper_axes_center");
|
||||||
buildSimpleGltf(doc, [0, 0, 0], [0], [1, 1, 1, 1], transform, '__helper_axes_center', WebGL2RenderingContext.POINTS);
|
buildSimpleGltf(
|
||||||
|
doc,
|
||||||
|
[0, 0, 0],
|
||||||
|
[0],
|
||||||
|
[1, 1, 1, 1],
|
||||||
|
transform,
|
||||||
|
"__helper_axes_center",
|
||||||
|
WebGL2RenderingContext.POINTS,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,8 +135,8 @@ export function newGridBox(doc: Document, size: Vector3, baseTransform: Matrix4,
|
|||||||
for (let axis of [new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, -1)]) {
|
for (let axis of [new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, -1)]) {
|
||||||
for (let positive of [1, -1]) {
|
for (let positive of [1, -1]) {
|
||||||
let offset = axis.clone().multiply(size.clone().multiplyScalar(0.5 * positive));
|
let offset = axis.clone().multiply(size.clone().multiplyScalar(0.5 * positive));
|
||||||
let translation = new Matrix4().makeTranslation(offset.x, offset.y, offset.z)
|
let translation = new Matrix4().makeTranslation(offset.x, offset.y, offset.z);
|
||||||
let rotation = new Matrix4().lookAt(new Vector3(), offset, new Vector3(0, 1, 0))
|
let rotation = new Matrix4().lookAt(new Vector3(), offset, new Vector3(0, 1, 0));
|
||||||
let size2 = new Vector2();
|
let size2 = new Vector2();
|
||||||
if (axis.x) size2.set(size.z, size.y);
|
if (axis.x) size2.set(size.z, size.y);
|
||||||
if (axis.y) size2.set(size.x, size.z);
|
if (axis.y) size2.set(size.x, size.z);
|
||||||
@@ -109,8 +155,16 @@ export function newGridBox(doc: Document, size: Vector3, baseTransform: Matrix4,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let colors = new Array(allPositions.length / 3 * 4).fill(1);
|
let colors = new Array((allPositions.length / 3) * 4).fill(1);
|
||||||
buildSimpleGltf(doc, allPositions, allIndices, colors, baseTransform, '__helper_grid', WebGL2RenderingContext.TRIANGLES);
|
buildSimpleGltf(
|
||||||
|
doc,
|
||||||
|
allPositions,
|
||||||
|
allIndices,
|
||||||
|
colors,
|
||||||
|
baseTransform,
|
||||||
|
"__helper_grid",
|
||||||
|
WebGL2RenderingContext.TRIANGLES,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function newGridPlane(size: Vector2, divisions = 10, divisionWidth = 0.002): [number[], number[]] {
|
export function newGridPlane(size: Vector2, divisions = 10, divisionWidth = 0.002): [number[], number[]] {
|
||||||
@@ -118,23 +172,23 @@ export function newGridPlane(size: Vector2, divisions = 10, divisionWidth = 0.00
|
|||||||
const rawIndices = [];
|
const rawIndices = [];
|
||||||
// Build the grid as triangles
|
// Build the grid as triangles
|
||||||
for (let i = 0; i <= divisions; i++) {
|
for (let i = 0; i <= divisions; i++) {
|
||||||
const x = -size.x / 2 + size.x * i / divisions;
|
const x = -size.x / 2 + (size.x * i) / divisions;
|
||||||
const y = -size.y / 2 + size.y * i / divisions;
|
const y = -size.y / 2 + (size.y * i) / divisions;
|
||||||
|
|
||||||
// Vertical quad (two triangles)
|
// Vertical quad (two triangles)
|
||||||
rawPositions.push(x - divisionWidth * size.x / 2, -size.y / 2, 0);
|
rawPositions.push(x - (divisionWidth * size.x) / 2, -size.y / 2, 0);
|
||||||
rawPositions.push(x + divisionWidth * size.x / 2, -size.y / 2, 0);
|
rawPositions.push(x + (divisionWidth * size.x) / 2, -size.y / 2, 0);
|
||||||
rawPositions.push(x + divisionWidth * size.x / 2, size.y / 2, 0);
|
rawPositions.push(x + (divisionWidth * size.x) / 2, size.y / 2, 0);
|
||||||
rawPositions.push(x - divisionWidth * size.x / 2, size.y / 2, 0);
|
rawPositions.push(x - (divisionWidth * size.x) / 2, size.y / 2, 0);
|
||||||
const baseIndex = i * 4;
|
const baseIndex = i * 4;
|
||||||
rawIndices.push(baseIndex, baseIndex + 1, baseIndex + 2);
|
rawIndices.push(baseIndex, baseIndex + 1, baseIndex + 2);
|
||||||
rawIndices.push(baseIndex, baseIndex + 2, baseIndex + 3);
|
rawIndices.push(baseIndex, baseIndex + 2, baseIndex + 3);
|
||||||
|
|
||||||
// Horizontal quad (two triangles)
|
// Horizontal quad (two triangles)
|
||||||
rawPositions.push(-size.x / 2, y - divisionWidth * size.y / 2, 0);
|
rawPositions.push(-size.x / 2, y - (divisionWidth * size.y) / 2, 0);
|
||||||
rawPositions.push(size.x / 2, y - divisionWidth * size.y / 2, 0);
|
rawPositions.push(size.x / 2, y - (divisionWidth * size.y) / 2, 0);
|
||||||
rawPositions.push(size.x / 2, y + divisionWidth * size.y / 2, 0);
|
rawPositions.push(size.x / 2, y + (divisionWidth * size.y) / 2, 0);
|
||||||
rawPositions.push(-size.x / 2, y + divisionWidth * size.y / 2, 0);
|
rawPositions.push(-size.x / 2, y + (divisionWidth * size.y) / 2, 0);
|
||||||
const baseIndex2 = (divisions + 1 + i) * 4;
|
const baseIndex2 = (divisions + 1 + i) * 4;
|
||||||
rawIndices.push(baseIndex2, baseIndex2 + 1, baseIndex2 + 2);
|
rawIndices.push(baseIndex2, baseIndex2 + 1, baseIndex2 + 2);
|
||||||
rawIndices.push(baseIndex2, baseIndex2 + 2, baseIndex2 + 3);
|
rawIndices.push(baseIndex2, baseIndex2 + 2, baseIndex2 + 3);
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export const settings = (async () => {
|
|||||||
url = "dev+http://localhost:32323";
|
url = "dev+http://localhost:32323";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
settings.preload[i] = url;
|
settings.preload[i] = url ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-decompress the code and other playground settings
|
// Auto-decompress the code and other playground settings
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const props = defineProps<{
|
|||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{ remove: [] }>()
|
const emit = defineEmits<{ remove: [] }>()
|
||||||
|
|
||||||
let modelName = props.meshes[0].getExtras()[extrasNameKey] // + " blah blah blah blah blag blah blah blah"
|
let modelName = props.meshes[0]?.getExtras()?.[extrasNameKey] // + " blah blah blah blah blag blah blah blah"
|
||||||
|
|
||||||
// Count the number of faces, edges and vertices
|
// Count the number of faces, edges and vertices
|
||||||
let faceCount = ref(-1);
|
let faceCount = ref(-1);
|
||||||
@@ -169,9 +169,9 @@ function onClipPlanesChange() {
|
|||||||
new Plane(new Vector3(0, -1, 0), offsetY).applyMatrix4(rotSceneMatrix),
|
new Plane(new Vector3(0, -1, 0), offsetY).applyMatrix4(rotSceneMatrix),
|
||||||
new Plane(new Vector3(0, 0, 1), -offsetZ).applyMatrix4(rotSceneMatrix),
|
new Plane(new Vector3(0, 0, 1), -offsetZ).applyMatrix4(rotSceneMatrix),
|
||||||
];
|
];
|
||||||
if (clipPlaneSwappedX.value) planes[0].negate();
|
if (clipPlaneSwappedX.value) planes[0]?.negate();
|
||||||
if (clipPlaneSwappedY.value) planes[1].negate();
|
if (clipPlaneSwappedY.value) planes[1]?.negate();
|
||||||
if (clipPlaneSwappedZ.value) planes[2].negate();
|
if (clipPlaneSwappedZ.value) planes[2]?.negate();
|
||||||
if (!enabledZ) planes.pop();
|
if (!enabledZ) planes.pop();
|
||||||
if (!enabledY) planes.splice(1, 1);
|
if (!enabledY) planes.splice(1, 1);
|
||||||
if (!enabledX) planes.shift();
|
if (!enabledX) planes.shift();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function meshesList(sceneDocument: Document): Array<Array<Mesh>> {
|
|||||||
// Grouped by shared name
|
// Grouped by shared name
|
||||||
return sceneDocument.getRoot().listMeshes().reduce((acc, mesh) => {
|
return sceneDocument.getRoot().listMeshes().reduce((acc, mesh) => {
|
||||||
let name = mesh.getExtras()[extrasNameKey]?.toString() ?? 'Unnamed';
|
let name = mesh.getExtras()[extrasNameKey]?.toString() ?? 'Unnamed';
|
||||||
let group = acc.find((group) => meshName(group[0]) === name);
|
let group = acc.find((group) => group[0] && meshName(group[0]) === name);
|
||||||
if (group) {
|
if (group) {
|
||||||
group.push(mesh);
|
group.push(mesh);
|
||||||
} else {
|
} else {
|
||||||
@@ -43,9 +43,9 @@ defineExpose({findModel})
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-expansion-panels v-for="meshes in meshesList(sceneDocument)" :key="meshName(meshes[0])"
|
<v-expansion-panels v-for="meshes in meshesList(sceneDocument)" :key="meshes[0] ? meshName(meshes[0]) : 'unnamed'"
|
||||||
v-model="expandedNames as any" multiple>
|
v-model="expandedNames as any" multiple>
|
||||||
<model :meshes="meshes" :viewer="props.viewer" @remove="onRemove(meshes[0])"/>
|
<model :meshes="meshes" :viewer="props.viewer" @remove="meshes[0] ? onRemove(meshes[0]) : undefined"/>
|
||||||
</v-expansion-panels>
|
</v-expansion-panels>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ function toggleSelection() {
|
|||||||
function toggleOpenNextSelection() {
|
function toggleOpenNextSelection() {
|
||||||
openNextSelection.value = [
|
openNextSelection.value = [
|
||||||
!openNextSelection.value[0],
|
!openNextSelection.value[0],
|
||||||
openNextSelection.value[0] ? openNextSelection.value[1] : selectionEnabled.value
|
openNextSelection.value[0] ? (openNextSelection.value[1] ?? false) : selectionEnabled.value
|
||||||
];
|
];
|
||||||
if (openNextSelection.value[0]) {
|
if (openNextSelection.value[0]) {
|
||||||
// Reuse selection code to identify the model
|
// Reuse selection code to identify the model
|
||||||
@@ -330,14 +330,20 @@ function updateBoundingBox() {
|
|||||||
// Only draw one edge per axis, the 2nd closest one to the camera
|
// Only draw one edge per axis, the 2nd closest one to the camera
|
||||||
for (let edgeI in edgesByAxis) {
|
for (let edgeI in edgesByAxis) {
|
||||||
let axisEdges = edgesByAxis[edgeI];
|
let axisEdges = edgesByAxis[edgeI];
|
||||||
let edge: Array<number> = axisEdges[0];
|
if (!axisEdges || axisEdges.length === 0) continue;
|
||||||
|
let edge: Array<number> = axisEdges[0] ?? [];
|
||||||
for (let i = 0; i < 2; i++) { // Find the 2nd closest one by running twice dropping the first
|
for (let i = 0; i < 2; i++) { // Find the 2nd closest one by running twice dropping the first
|
||||||
edge = axisEdges[0];
|
if (!axisEdges || axisEdges.length === 0) break;
|
||||||
|
edge = axisEdges[0] ?? [];
|
||||||
let edgeDist = Infinity;
|
let edgeDist = Infinity;
|
||||||
let cameraPos: Vector3 = props.viewer?.scene?.camera?.position ?? new Vector3();
|
let cameraPos: Vector3 = props.viewer?.scene?.camera?.position ?? new Vector3();
|
||||||
for (let testEdge of axisEdges) {
|
for (let testEdge of axisEdges) {
|
||||||
let from = new Vector3(...corners[testEdge[0]]);
|
if (!testEdge || testEdge.length < 2) continue;
|
||||||
let to = new Vector3(...corners[testEdge[1]]);
|
let cornerA = corners[testEdge[0] ?? 0];
|
||||||
|
let cornerB = corners[testEdge[1] ?? 0];
|
||||||
|
if (!cornerA || !cornerB) continue;
|
||||||
|
let from = new Vector3(...cornerA);
|
||||||
|
let to = new Vector3(...cornerB);
|
||||||
let mid = from.clone().add(to).multiplyScalar(0.5);
|
let mid = from.clone().add(to).multiplyScalar(0.5);
|
||||||
let newDist = cameraPos.distanceTo(mid);
|
let newDist = cameraPos.distanceTo(mid);
|
||||||
if (newDist < edgeDist) {
|
if (newDist < edgeDist) {
|
||||||
@@ -347,11 +353,16 @@ function updateBoundingBox() {
|
|||||||
}
|
}
|
||||||
axisEdges = axisEdges.filter((e) => e !== edge);
|
axisEdges = axisEdges.filter((e) => e !== edge);
|
||||||
}
|
}
|
||||||
let from = new Vector3(...corners[edge[0]]);
|
if (!edge || edge.length < 2) continue;
|
||||||
let to = new Vector3(...corners[edge[1]]);
|
let cornerA = corners[edge[0] ?? 0];
|
||||||
|
let cornerB = corners[edge[1] ?? 0];
|
||||||
|
if (!cornerA || !cornerB) continue;
|
||||||
|
let from = new Vector3(...cornerA);
|
||||||
|
let to = new Vector3(...cornerB);
|
||||||
let length = to.clone().sub(from).length();
|
let length = to.clone().sub(from).length();
|
||||||
if (length < 0.05) continue; // Skip very small edges (e.g. a single point)
|
if (length < 0.05) continue; // Skip very small edges (e.g. a single point)
|
||||||
let color = [AxesColors.x, AxesColors.y, AxesColors.z][edgeI][1]; // Secondary colors
|
let colorArray = [AxesColors.x, AxesColors.y, AxesColors.z][parseInt(edgeI)];
|
||||||
|
let color = colorArray ? colorArray[1] : [255, 255, 255]; // Secondary colors
|
||||||
let lineCacheKey = JSON.stringify([from, to]);
|
let lineCacheKey = JSON.stringify([from, to]);
|
||||||
let matchingLine = boundingBoxLines[lineCacheKey];
|
let matchingLine = boundingBoxLines[lineCacheKey];
|
||||||
if (matchingLine) {
|
if (matchingLine) {
|
||||||
@@ -359,7 +370,7 @@ function updateBoundingBox() {
|
|||||||
} else {
|
} else {
|
||||||
let newLineId = props.viewer?.addLine3D(from, to,
|
let newLineId = props.viewer?.addLine3D(from, to,
|
||||||
length.toFixed(1) + "mm", {
|
length.toFixed(1) + "mm", {
|
||||||
"stroke": "rgb(" + color.join(',') + ")",
|
"stroke": "rgb(" + (color ?? [255, 255, 255]).join(',') + ")",
|
||||||
"stroke-width": "2"
|
"stroke-width": "2"
|
||||||
});
|
});
|
||||||
if (newLineId) {
|
if (newLineId) {
|
||||||
@@ -410,10 +421,17 @@ function updateDistances() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add lines (if not already added)
|
// Add lines (if not already added)
|
||||||
let {min, center, max} = distances(selected.value[0], selected.value[1], props.viewer?.scene);
|
if (!selected.value[0] || !selected.value[1] || !props.viewer?.scene) return;
|
||||||
|
let {min, center, max} = distances(selected.value[0], selected.value[1], props.viewer.scene);
|
||||||
|
if (max[0] && max[1]) {
|
||||||
ensureLine(max[0], max[1], max[1].distanceTo(max[0]).toFixed(1) + "mm", "orange");
|
ensureLine(max[0], max[1], max[1].distanceTo(max[0]).toFixed(1) + "mm", "orange");
|
||||||
|
}
|
||||||
|
if (center[0] && center[1]) {
|
||||||
ensureLine(center[0], center[1], center[1].distanceTo(center[0]).toFixed(1) + "mm", "green");
|
ensureLine(center[0], center[1], center[1].distanceTo(center[0]).toFixed(1) + "mm", "green");
|
||||||
|
}
|
||||||
|
if (min[0] && min[1]) {
|
||||||
ensureLine(min[0], min[1], min[1].distanceTo(min[0]).toFixed(1) + "mm", "cyan");
|
ensureLine(min[0], min[1], min[1].distanceTo(min[0]).toFixed(1) + "mm", "cyan");
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the lines that are no longer needed
|
// Remove the lines that are no longer needed
|
||||||
for (let lineLocator of distanceLinesToRemove) {
|
for (let lineLocator of distanceLinesToRemove) {
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
// Model management from the graphics side
|
// Model management from the graphics side
|
||||||
|
|
||||||
import type {MObject3D} from "./Selection.vue";
|
import type { MObject3D } from "./Selection.vue";
|
||||||
import type {Intersection} from "three";
|
import type { Intersection } from "three";
|
||||||
import {Box3} from "three";
|
import { Box3 } from "three";
|
||||||
import {extrasNameKey} from "../misc/gltf";
|
import { extrasNameKey } from "../misc/gltf";
|
||||||
|
|
||||||
/** Information about a single item in the selection */
|
/** Information about a single item in the selection */
|
||||||
export class SelectionInfo {
|
export class SelectionInfo {
|
||||||
/** The object which was (partially) selected */
|
/** The object which was (partially) selected */
|
||||||
object: MObject3D
|
object: MObject3D;
|
||||||
/** The type of the selection */
|
/** The type of the selection */
|
||||||
kind: 'face' | 'edge' | 'vertex'
|
kind: "face" | "edge" | "vertex";
|
||||||
/** Start and end indices of the primitives in the geometry */
|
/** Start and end indices of the primitives in the geometry */
|
||||||
indices: [number, number]
|
indices: [number, number];
|
||||||
|
|
||||||
constructor(object: MObject3D, kind: 'face' | 'edge' | 'vertex', indices: [number, number]) {
|
constructor(object: MObject3D, kind: "face" | "edge" | "vertex", indices: [number, number]) {
|
||||||
this.object = object;
|
this.object = object;
|
||||||
this.kind = kind;
|
this.kind = kind;
|
||||||
this.indices = indices;
|
this.indices = indices;
|
||||||
@@ -25,10 +25,12 @@ export class SelectionInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public matches(object: MObject3D) {
|
public matches(object: MObject3D) {
|
||||||
return this.getObjectName() === object.userData[extrasNameKey] &&
|
return (
|
||||||
(this.kind === 'face' && (object.type === 'Mesh' || object.type === 'SkinnedMesh') ||
|
this.getObjectName() === object.userData[extrasNameKey] &&
|
||||||
this.kind === 'edge' && (object.type === 'Line' || object.type === 'LineSegments') ||
|
((this.kind === "face" && (object.type === "Mesh" || object.type === "SkinnedMesh")) ||
|
||||||
this.kind === 'vertex' && object.type === 'Points')
|
(this.kind === "edge" && (object.type === "Line" || object.type === "LineSegments")) ||
|
||||||
|
(this.kind === "vertex" && object.type === "Points"))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getKey() {
|
public getKey() {
|
||||||
@@ -36,8 +38,8 @@ export class SelectionInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getBox(): Box3 {
|
public getBox(): Box3 {
|
||||||
let index = this.object.geometry.index || {getX: (i: number) => i};
|
let index = this.object.geometry.index || { getX: (i: number) => i };
|
||||||
let pos = this.object.geometry.getAttribute('position');
|
let pos = this.object.geometry.getAttribute("position");
|
||||||
let min = [Infinity, Infinity, Infinity];
|
let min = [Infinity, Infinity, Infinity];
|
||||||
let max = [-Infinity, -Infinity, -Infinity];
|
let max = [-Infinity, -Infinity, -Infinity];
|
||||||
for (let i = this.indices[0]; i < this.indices[1]; i++) {
|
for (let i = this.indices[0]; i < this.indices[1]; i++) {
|
||||||
@@ -45,12 +47,12 @@ export class SelectionInfo {
|
|||||||
let x = pos.getX(vertIndex);
|
let x = pos.getX(vertIndex);
|
||||||
let y = pos.getY(vertIndex);
|
let y = pos.getY(vertIndex);
|
||||||
let z = pos.getZ(vertIndex);
|
let z = pos.getZ(vertIndex);
|
||||||
min[0] = Math.min(min[0], x);
|
min[0] = Math.min(min[0] ?? Infinity, x);
|
||||||
min[1] = Math.min(min[1], y);
|
min[1] = Math.min(min[1] ?? Infinity, y);
|
||||||
min[2] = Math.min(min[2], z);
|
min[2] = Math.min(min[2] ?? Infinity, z);
|
||||||
max[0] = Math.max(max[0], x);
|
max[0] = Math.max(max[0] ?? -Infinity, x);
|
||||||
max[1] = Math.max(max[1], y);
|
max[1] = Math.max(max[1] ?? -Infinity, y);
|
||||||
max[2] = Math.max(max[2], z);
|
max[2] = Math.max(max[2] ?? -Infinity, z);
|
||||||
}
|
}
|
||||||
return new Box3().setFromArray([...min, ...max]);
|
return new Box3().setFromArray([...min, ...max]);
|
||||||
}
|
}
|
||||||
@@ -58,20 +60,20 @@ export class SelectionInfo {
|
|||||||
|
|
||||||
export function hitToSelectionInfo(hit: Intersection<MObject3D>): SelectionInfo | null {
|
export function hitToSelectionInfo(hit: Intersection<MObject3D>): SelectionInfo | null {
|
||||||
let kind = hit.object.type;
|
let kind = hit.object.type;
|
||||||
if (kind == 'Mesh' || kind == 'SkinnedMesh') {
|
if (kind == "Mesh" || kind == "SkinnedMesh") {
|
||||||
let indices = hitFaceTriangleIndices(hit);
|
let indices = hitFaceTriangleIndices(hit);
|
||||||
if (indices === null) return null;
|
if (indices === null) return null;
|
||||||
return new SelectionInfo(hit.object, 'face', indices);
|
return new SelectionInfo(hit.object, "face", indices);
|
||||||
} else if (kind == 'Line' || kind == 'LineSegments') {
|
} else if (kind == "Line" || kind == "LineSegments") {
|
||||||
// Select raw lines, not the wide meshes representing them
|
// Select raw lines, not the wide meshes representing them
|
||||||
// This is because the indices refer to the raw lines, not the wide meshes
|
// This is because the indices refer to the raw lines, not the wide meshes
|
||||||
// Furthermore, this allows better "fuzzy" raycasting logic
|
// Furthermore, this allows better "fuzzy" raycasting logic
|
||||||
let indices = hitEdgePointIndices(hit);
|
let indices = hitEdgePointIndices(hit);
|
||||||
if (indices === null) return null;
|
if (indices === null) return null;
|
||||||
return new SelectionInfo(hit.object, 'edge', indices);
|
return new SelectionInfo(hit.object, "edge", indices);
|
||||||
} else if (kind == 'Points') {
|
} else if (kind == "Points") {
|
||||||
if (hit.index === undefined) return null;
|
if (hit.index === undefined) return null;
|
||||||
return new SelectionInfo(hit.object, 'vertex', [hit.index, hit.index + 1]);
|
return new SelectionInfo(hit.object, "vertex", [hit.index, hit.index + 1]);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -79,13 +81,15 @@ export function hitToSelectionInfo(hit: Intersection<MObject3D>): SelectionInfo
|
|||||||
function hitFaceTriangleIndices(hit: Intersection<MObject3D>): [number, number] | null {
|
function hitFaceTriangleIndices(hit: Intersection<MObject3D>): [number, number] | null {
|
||||||
let faceTrianglesEnd = hit?.object?.geometry?.userData?.face_triangles_end;
|
let faceTrianglesEnd = hit?.object?.geometry?.userData?.face_triangles_end;
|
||||||
if (!hit.faceIndex) return null;
|
if (!hit.faceIndex) return null;
|
||||||
if (!faceTrianglesEnd) { // Fallback to selecting the whole imported mesh
|
if (!faceTrianglesEnd) {
|
||||||
|
// Fallback to selecting the whole imported mesh
|
||||||
//console.log("No face_triangles_end found, selecting the whole mesh");
|
//console.log("No face_triangles_end found, selecting the whole mesh");
|
||||||
return [0, (hit.object.geometry.index ?? hit.object.geometry.attributes.position).count];
|
return [0, (hit.object.geometry.index ?? hit.object.geometry.attributes.position)?.count ?? 0];
|
||||||
} else { // Normal CAD model
|
} else {
|
||||||
|
// Normal CAD model
|
||||||
let rawIndex = hit.faceIndex * 3; // Faces are triangles with 3 indices
|
let rawIndex = hit.faceIndex * 3; // Faces are triangles with 3 indices
|
||||||
for (let i = 0; i < faceTrianglesEnd.length; i++) {
|
for (let i = 0; i < faceTrianglesEnd.length; i++) {
|
||||||
let faceSwapIndex = faceTrianglesEnd[i]
|
let faceSwapIndex = faceTrianglesEnd[i];
|
||||||
if (rawIndex < faceSwapIndex) {
|
if (rawIndex < faceSwapIndex) {
|
||||||
let start = i === 0 ? 0 : faceTrianglesEnd[i - 1];
|
let start = i === 0 ? 0 : faceTrianglesEnd[i - 1];
|
||||||
return [start, faceTrianglesEnd[i]];
|
return [start, faceTrianglesEnd[i]];
|
||||||
@@ -100,7 +104,7 @@ function hitEdgePointIndices(hit: Intersection<MObject3D>): [number, number] | n
|
|||||||
if (!edgePointsEnd || hit.index === undefined) return null;
|
if (!edgePointsEnd || hit.index === undefined) return null;
|
||||||
let rawIndex = hit.index; // Faces are triangles with 3 indices
|
let rawIndex = hit.index; // Faces are triangles with 3 indices
|
||||||
for (let i = 0; i < edgePointsEnd.length; i++) {
|
for (let i = 0; i < edgePointsEnd.length; i++) {
|
||||||
let edgeSwapIndex = edgePointsEnd[i]
|
let edgeSwapIndex = edgePointsEnd[i];
|
||||||
if (rawIndex < edgeSwapIndex) {
|
if (rawIndex < edgeSwapIndex) {
|
||||||
let start = i === 0 ? 0 : edgePointsEnd[i - 1];
|
let start = i === 0 ? 0 : edgePointsEnd[i - 1];
|
||||||
return [start, edgePointsEnd[i]];
|
return [start, edgePointsEnd[i]];
|
||||||
@@ -109,13 +113,23 @@ function hitEdgePointIndices(hit: Intersection<MObject3D>): [number, number] | n
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyColor(selInfo: SelectionInfo, colorAttribute: any, color: [number, number, number, number]): [number, number, number, number] {
|
function applyColor(
|
||||||
let index = selInfo.object.geometry.index
|
selInfo: SelectionInfo,
|
||||||
|
colorAttribute: any,
|
||||||
|
color: [number, number, number, number],
|
||||||
|
): [number, number, number, number] {
|
||||||
|
let index = selInfo.object.geometry.index;
|
||||||
let prevColor: [number, number, number, number] | null = null;
|
let prevColor: [number, number, number, number] | null = null;
|
||||||
if (colorAttribute !== undefined) {
|
if (colorAttribute !== undefined) {
|
||||||
for (let i = selInfo.indices[0]; i < selInfo.indices[1]; i++) {
|
for (let i = selInfo.indices[0]; i < selInfo.indices[1]; i++) {
|
||||||
let vertIndex = index!.getX(i);
|
let vertIndex = index!.getX(i);
|
||||||
if (prevColor === null) prevColor = [colorAttribute.getX(vertIndex), colorAttribute.getY(vertIndex), colorAttribute.getZ(vertIndex), colorAttribute.getW(vertIndex)];
|
if (prevColor === null)
|
||||||
|
prevColor = [
|
||||||
|
colorAttribute.getX(vertIndex),
|
||||||
|
colorAttribute.getY(vertIndex),
|
||||||
|
colorAttribute.getZ(vertIndex),
|
||||||
|
colorAttribute.getW(vertIndex),
|
||||||
|
];
|
||||||
colorAttribute.setXYZW(vertIndex, color[0], color[1], color[2], color[3]);
|
colorAttribute.setXYZW(vertIndex, color[0], color[1], color[2], color[3]);
|
||||||
}
|
}
|
||||||
colorAttribute.needsUpdate = true;
|
colorAttribute.needsUpdate = true;
|
||||||
@@ -127,7 +141,11 @@ function applyColor(selInfo: SelectionInfo, colorAttribute: any, color: [number,
|
|||||||
if (indexAttribute.getX(i) >= selInfo.indices[0] && indexAttribute.getX(i) < selInfo.indices[1]) {
|
if (indexAttribute.getX(i) >= selInfo.indices[0] && indexAttribute.getX(i) < selInfo.indices[1]) {
|
||||||
allNewColors.push(color[0], color[1], color[2]);
|
allNewColors.push(color[0], color[1], color[2]);
|
||||||
} else {
|
} else {
|
||||||
allNewColors.push(colorAttribute.getX(indexAttribute.getX(i)), colorAttribute.getY(indexAttribute.getX(i)), colorAttribute.getZ(indexAttribute.getX(i)));
|
allNewColors.push(
|
||||||
|
colorAttribute.getX(indexAttribute.getX(i)),
|
||||||
|
colorAttribute.getY(indexAttribute.getX(i)),
|
||||||
|
colorAttribute.getZ(indexAttribute.getX(i)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
selInfo.object.userData.niceLine.geometry.setColors(allNewColors);
|
selInfo.object.userData.niceLine.geometry.setColors(allNewColors);
|
||||||
@@ -135,7 +153,8 @@ function applyColor(selInfo: SelectionInfo, colorAttribute: any, color: [number,
|
|||||||
(attribute as any).needsUpdate = true;
|
(attribute as any).needsUpdate = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // Fallback to tinting the whole mesh for imported models
|
} else {
|
||||||
|
// Fallback to tinting the whole mesh for imported models
|
||||||
//console.log("No color attribute found, tinting the whole mesh")
|
//console.log("No color attribute found, tinting the whole mesh")
|
||||||
let tmpPrevColor = selInfo.object.material.color;
|
let tmpPrevColor = selInfo.object.material.color;
|
||||||
prevColor = [tmpPrevColor.r, tmpPrevColor.g, tmpPrevColor.b, 1];
|
prevColor = [tmpPrevColor.r, tmpPrevColor.g, tmpPrevColor.b, 1];
|
||||||
@@ -148,7 +167,7 @@ function applyColor(selInfo: SelectionInfo, colorAttribute: any, color: [number,
|
|||||||
export function highlight(selInfo: SelectionInfo): void {
|
export function highlight(selInfo: SelectionInfo): void {
|
||||||
// Update the color of all the triangles in the face
|
// Update the color of all the triangles in the face
|
||||||
let geometry = selInfo.object.geometry;
|
let geometry = selInfo.object.geometry;
|
||||||
let colorAttr = selInfo.object.geometry.getAttribute('color');
|
let colorAttr = selInfo.object.geometry.getAttribute("color");
|
||||||
geometry.userData.savedColor = geometry.userData.savedColor || {};
|
geometry.userData.savedColor = geometry.userData.savedColor || {};
|
||||||
geometry.userData.savedColor[selInfo.getKey()] = applyColor(selInfo, colorAttr, [1.0, 0.0, 0.0, 1.0]);
|
geometry.userData.savedColor[selInfo.getKey()] = applyColor(selInfo, colorAttr, [1.0, 0.0, 0.0, 1.0]);
|
||||||
}
|
}
|
||||||
@@ -156,7 +175,7 @@ export function highlight(selInfo: SelectionInfo): void {
|
|||||||
export function highlightUndo(selInfo: SelectionInfo): void {
|
export function highlightUndo(selInfo: SelectionInfo): void {
|
||||||
// Update the color of all the triangles in the face
|
// Update the color of all the triangles in the face
|
||||||
let geometry = selInfo.object.geometry;
|
let geometry = selInfo.object.geometry;
|
||||||
let colorAttr = selInfo.object.geometry.getAttribute('color');
|
let colorAttr = selInfo.object.geometry.getAttribute("color");
|
||||||
let savedColor = geometry.userData.savedColor[selInfo.getKey()];
|
let savedColor = geometry.userData.savedColor[selInfo.getKey()];
|
||||||
applyColor(selInfo, colorAttr, savedColor);
|
applyColor(selInfo, colorAttr, savedColor);
|
||||||
delete geometry.userData.savedColor[selInfo.getKey()];
|
delete geometry.userData.savedColor[selInfo.getKey()];
|
||||||
|
|||||||
@@ -161,9 +161,9 @@ function addLine3D(p1: Vector3, p2: Vector3, centerText?: string, lineAttrs: { [
|
|||||||
function removeLine3D(id: number): boolean {
|
function removeLine3D(id: number): boolean {
|
||||||
if (!scene.value || !(id in lines.value)) return false;
|
if (!scene.value || !(id in lines.value)) return false;
|
||||||
scene.value.removeHotspot(new Hotspot({name: 'line' + id + '_start'}));
|
scene.value.removeHotspot(new Hotspot({name: 'line' + id + '_start'}));
|
||||||
lines.value[id].startHotspot.parentElement?.remove()
|
lines.value[id]?.startHotspot.parentElement?.remove()
|
||||||
scene.value.removeHotspot(new Hotspot({name: 'line' + id + '_end'}));
|
scene.value.removeHotspot(new Hotspot({name: 'line' + id + '_end'}));
|
||||||
lines.value[id].endHotspot.parentElement?.remove()
|
lines.value[id]?.endHotspot.parentElement?.remove()
|
||||||
delete lines.value[id];
|
delete lines.value[id];
|
||||||
scene.value.queueRender() // Needed to update the hotspots
|
scene.value.queueRender() // Needed to update the hotspots
|
||||||
return true;
|
return true;
|
||||||
@@ -175,17 +175,17 @@ function onCameraChangeLine(lineId: number) {
|
|||||||
if (!(lineId in lines.value) || !(elem.value)) return // Silently ignore (not updated yet)
|
if (!(lineId in lines.value) || !(elem.value)) return // Silently ignore (not updated yet)
|
||||||
// Update start and end 2D positions
|
// Update start and end 2D positions
|
||||||
let {x: xB, y: yB} = elem.value.getBoundingClientRect();
|
let {x: xB, y: yB} = elem.value.getBoundingClientRect();
|
||||||
let {x, y} = lines.value[lineId].startHotspot.getBoundingClientRect();
|
let {x, y} = lines.value[lineId]?.startHotspot.getBoundingClientRect() ?? {x: 0, y: 0};
|
||||||
lines.value[lineId].start2D = [x - xB, y - yB];
|
if (lines.value[lineId]) lines.value[lineId].start2D = [x - xB, y - yB];
|
||||||
let {x: x2, y: y2} = lines.value[lineId].endHotspot.getBoundingClientRect();
|
let {x: x2, y: y2} = lines.value[lineId]?.endHotspot.getBoundingClientRect() ?? {x: 0, y: 0};
|
||||||
lines.value[lineId].end2D = [x2 - xB, y2 - yB];
|
if (lines.value[lineId]) lines.value[lineId].end2D = [x2 - xB, y2 - yB];
|
||||||
|
|
||||||
// Update the center text size if needed
|
// Update the center text size if needed
|
||||||
if (svg.value && lines.value[lineId].centerText && lines.value[lineId].centerTextSize[0] === 0) {
|
if (svg.value && lines.value[lineId]?.centerText && lines.value[lineId]?.centerTextSize[0] === 0) {
|
||||||
let text = svg.value.getElementsByClassName('line' + lineId + '_text')[0] as SVGTextElement | undefined;
|
let text = svg.value.getElementsByClassName('line' + lineId + '_text')[0] as SVGTextElement | undefined;
|
||||||
if (text) {
|
if (text) {
|
||||||
let bbox = text.getBBox();
|
let bbox = text.getBBox();
|
||||||
lines.value[lineId].centerTextSize = [bbox.width, bbox.height];
|
if (lines.value[lineId]) lines.value[lineId].centerTextSize = [bbox.width, bbox.height];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {ModelViewerElement} from '@google/model-viewer';
|
import { ModelViewerElement } from "@google/model-viewer";
|
||||||
import {$scene} from "@google/model-viewer/lib/model-viewer-base";
|
import { $scene } from "@google/model-viewer/lib/model-viewer-base";
|
||||||
import {settings} from "../misc/settings.ts";
|
import { settings } from "../misc/settings.ts";
|
||||||
|
|
||||||
export let currentSceneRotation = 0; // radians, 0 is the default rotation
|
export let currentSceneRotation = 0; // radians, 0 is the default rotation
|
||||||
|
|
||||||
@@ -13,9 +13,9 @@ export async function setupLighting(modelViewer: ModelViewerElement) {
|
|||||||
|
|
||||||
const startPan = () => {
|
const startPan = () => {
|
||||||
const orbit = modelViewer.getCameraOrbit();
|
const orbit = modelViewer.getCameraOrbit();
|
||||||
const {radius} = orbit;
|
const { radius } = orbit;
|
||||||
radiansPerPixel = -1 * radius / modelViewer.getBoundingClientRect().height;
|
radiansPerPixel = (-1 * radius) / modelViewer.getBoundingClientRect().height;
|
||||||
modelViewer.interactionPrompt = 'none';
|
modelViewer.interactionPrompt = "none";
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatePan = (thisX: number) => {
|
const updatePan = (thisX: number) => {
|
||||||
@@ -27,50 +27,70 @@ export async function setupLighting(modelViewer: ModelViewerElement) {
|
|||||||
modelViewer.cameraOrbit = orbit.toString();
|
modelViewer.cameraOrbit = orbit.toString();
|
||||||
modelViewer.resetTurntableRotation(currentSceneRotation);
|
modelViewer.resetTurntableRotation(currentSceneRotation);
|
||||||
modelViewer.jumpCameraToGoal();
|
modelViewer.jumpCameraToGoal();
|
||||||
}
|
};
|
||||||
|
|
||||||
modelViewer.addEventListener('mousedown', (event) => {
|
modelViewer.addEventListener(
|
||||||
|
"mousedown",
|
||||||
|
(event) => {
|
||||||
panning = event.metaKey || event.shiftKey;
|
panning = event.metaKey || event.shiftKey;
|
||||||
if (!panning)
|
if (!panning) return;
|
||||||
return;
|
|
||||||
|
|
||||||
lastX = event.clientX;
|
lastX = event.clientX;
|
||||||
startPan();
|
startPan();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}, true);
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
modelViewer.addEventListener('touchstart', (event) => {
|
modelViewer.addEventListener(
|
||||||
const {targetTouches, touches} = event;
|
"touchstart",
|
||||||
|
(event) => {
|
||||||
|
const { targetTouches, touches } = event;
|
||||||
panning = targetTouches.length === 2 && targetTouches.length === touches.length;
|
panning = targetTouches.length === 2 && targetTouches.length === touches.length;
|
||||||
if (!panning)
|
if (!panning) return;
|
||||||
return;
|
|
||||||
|
|
||||||
lastX = 0.5 * (targetTouches[0].clientX + targetTouches[1].clientX);
|
lastX = 0.5 * ((targetTouches[0]?.clientX ?? 0) + (targetTouches[1]?.clientX ?? 0));
|
||||||
startPan();
|
startPan();
|
||||||
}, true);
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
document.addEventListener('mousemove', (event) => {
|
document.addEventListener(
|
||||||
if (!panning)
|
"mousemove",
|
||||||
return;
|
(event) => {
|
||||||
|
if (!panning) return;
|
||||||
|
|
||||||
updatePan(event.clientX);
|
updatePan(event.clientX);
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}, true);
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
modelViewer.addEventListener('touchmove', (event) => {
|
modelViewer.addEventListener(
|
||||||
if (!panning || event.targetTouches.length !== 2)
|
"touchmove",
|
||||||
return;
|
(event) => {
|
||||||
|
if (!panning || event.targetTouches.length !== 2) return;
|
||||||
|
|
||||||
const {targetTouches} = event;
|
const { targetTouches } = event;
|
||||||
const thisX = 0.5 * (targetTouches[0].clientX + targetTouches[1].clientX);
|
const thisX = 0.5 * ((targetTouches[0]?.clientX ?? 0) + (targetTouches[1]?.clientX ?? 0));
|
||||||
updatePan(thisX);
|
updatePan(thisX);
|
||||||
}, true);
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
document.addEventListener('mouseup', (event) => {
|
document.addEventListener(
|
||||||
|
"mouseup",
|
||||||
|
(event) => {
|
||||||
panning = false;
|
panning = false;
|
||||||
}, true);
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
modelViewer.addEventListener('touchend', (event) => {
|
modelViewer.addEventListener(
|
||||||
|
"touchend",
|
||||||
|
(event) => {
|
||||||
panning = false;
|
panning = false;
|
||||||
}, true);
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
"@types/three": "^0.179.0",
|
"@types/three": "^0.179.0",
|
||||||
"@vitejs/plugin-vue": "^6.0.0",
|
"@vitejs/plugin-vue": "^6.0.0",
|
||||||
"@vitejs/plugin-vue-jsx": "^5.0.0",
|
"@vitejs/plugin-vue-jsx": "^5.0.0",
|
||||||
"@vue/tsconfig": "^0.7.0",
|
"@vue/tsconfig": "^0.8.0",
|
||||||
"buffer": "^5.5.0||^6.0.0",
|
"buffer": "^5.5.0||^6.0.0",
|
||||||
"commander": "^14.0.0",
|
"commander": "^14.0.0",
|
||||||
"generate-license-file": "^4.0.0",
|
"generate-license-file": "^4.0.0",
|
||||||
|
|||||||
@@ -1269,10 +1269,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.20.tgz#8740b370738c8c7e29e02fa9051cfe6d20114cb4"
|
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.20.tgz#8740b370738c8c7e29e02fa9051cfe6d20114cb4"
|
||||||
integrity sha512-SoRGP596KU/ig6TfgkCMbXkr4YJ91n/QSdMuqeP5r3hVIYA3CPHUBCc7Skak0EAKV+5lL4KyIh61VA/pK1CIAA==
|
integrity sha512-SoRGP596KU/ig6TfgkCMbXkr4YJ91n/QSdMuqeP5r3hVIYA3CPHUBCc7Skak0EAKV+5lL4KyIh61VA/pK1CIAA==
|
||||||
|
|
||||||
"@vue/tsconfig@^0.7.0":
|
"@vue/tsconfig@^0.8.0":
|
||||||
version "0.7.0"
|
version "0.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.7.0.tgz#67044c847b7a137b8cbfd6b23104c36dbaf80d1d"
|
resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.8.1.tgz#4732251fa58945024424385cf3be0b1708fad5fe"
|
||||||
integrity sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==
|
integrity sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==
|
||||||
|
|
||||||
"@webgpu/types@*":
|
"@webgpu/types@*":
|
||||||
version "0.1.64"
|
version "0.1.64"
|
||||||
|
|||||||
Reference in New Issue
Block a user