Files
cad-Integration/text-to-cad-main/packages/cadjs/src/lib/selectors/runtime.js
T
2026-07-22 13:48:46 +08:00

894 lines
32 KiB
JavaScript

import { buildCadRefToken } from "../cadRefs.js";
function isObject(value) {
return !!value && typeof value === "object" && !Array.isArray(value);
}
function toRows(manifest, rowKey, columnsKey) {
const columns = manifest?.tables?.[columnsKey];
const rows = manifest?.[rowKey];
if (!Array.isArray(columns) || !Array.isArray(rows)) {
return [];
}
return rows
.filter(Array.isArray)
.map((row) => Object.fromEntries(columns.map((column, index) => [column, row[index]])));
}
function relationArray(manifest, buffers, relationKey, viewKey) {
const direct = manifest?.relations?.[relationKey];
if (Array.isArray(direct) || ArrayBuffer.isView(direct)) {
return direct;
}
const viewName = manifest?.relations?.[viewKey];
if (typeof viewName === "string" && buffers?.[viewName]) {
return buffers[viewName];
}
return [];
}
function typedBufferView(manifest, buffers, manifestSectionKey, viewKey) {
const viewName = manifest?.[manifestSectionKey]?.[viewKey];
if (typeof viewName === "string" && buffers?.[viewName]) {
return buffers[viewName];
}
return new Uint32Array(0);
}
function selectorPrefix(singleOccurrenceId, selector) {
if (!singleOccurrenceId || !selector.startsWith(`${singleOccurrenceId}.`)) {
return selector;
}
const suffix = selector.slice(singleOccurrenceId.length + 1);
return suffix.startsWith("s") || suffix.startsWith("f") || suffix.startsWith("e") ? suffix : selector;
}
function selectorTypeLabel(selectorType) {
if (selectorType === "occurrence") {
return "Occurrence";
}
if (selectorType === "shape") {
return "Shape";
}
if (selectorType === "face") {
return "Face";
}
return "Edge";
}
function transformPoint(transform, point) {
if (!Array.isArray(point) || point.length < 3) {
return point;
}
if (!Array.isArray(transform) || transform.length < 16) {
return [Number(point[0]), Number(point[1]), Number(point[2])];
}
const x = Number(point[0]);
const y = Number(point[1]);
const z = Number(point[2]);
return [
(transform[0] * x) + (transform[1] * y) + (transform[2] * z) + transform[3],
(transform[4] * x) + (transform[5] * y) + (transform[6] * z) + transform[7],
(transform[8] * x) + (transform[9] * y) + (transform[10] * z) + transform[11],
];
}
function normalizeVector(vector) {
const x = Number(vector?.[0] || 0);
const y = Number(vector?.[1] || 0);
const z = Number(vector?.[2] || 0);
const magnitude = Math.hypot(x, y, z);
if (magnitude <= 1e-9) {
return null;
}
return [x / magnitude, y / magnitude, z / magnitude];
}
function transformVector(transform, vector) {
if (!Array.isArray(vector) || vector.length < 3 || !Array.isArray(transform) || transform.length < 16) {
return normalizeVector(vector || []);
}
return normalizeVector([
(transform[0] * vector[0]) + (transform[1] * vector[1]) + (transform[2] * vector[2]),
(transform[4] * vector[0]) + (transform[5] * vector[1]) + (transform[6] * vector[2]),
(transform[8] * vector[0]) + (transform[9] * vector[1]) + (transform[10] * vector[2]),
]);
}
function transformBBox(transform, bbox) {
if (!isObject(bbox)) {
return bbox;
}
const min = Array.isArray(bbox.min) ? bbox.min : [0, 0, 0];
const max = Array.isArray(bbox.max) ? bbox.max : [0, 0, 0];
const corners = [
[min[0], min[1], min[2]],
[min[0], min[1], max[2]],
[min[0], max[1], min[2]],
[min[0], max[1], max[2]],
[max[0], min[1], min[2]],
[max[0], min[1], max[2]],
[max[0], max[1], min[2]],
[max[0], max[1], max[2]],
].map((point) => transformPoint(transform, point));
const xs = corners.map((point) => point[0]);
const ys = corners.map((point) => point[1]);
const zs = corners.map((point) => point[2]);
return {
min: [Math.min(...xs), Math.min(...ys), Math.min(...zs)],
max: [Math.max(...xs), Math.max(...ys), Math.max(...zs)],
};
}
function transformParams(transform, params) {
if (!isObject(params)) {
return params;
}
const pointKeys = new Set(["origin", "center", "location"]);
const vectorKeys = new Set(["axis", "direction", "normal"]);
return Object.fromEntries(Object.entries(params).map(([key, value]) => {
if (pointKeys.has(key) && Array.isArray(value) && value.length === 3) {
return [key, transformPoint(transform, value)];
}
if (vectorKeys.has(key) && Array.isArray(value) && value.length === 3) {
return [key, transformVector(transform, value)];
}
return [key, value];
}));
}
function transformPointList(transform, points) {
if (!Array.isArray(points)) {
return points;
}
return points.map((point) => (
Array.isArray(point) && point.length >= 3 ? transformPoint(transform, point) : point
));
}
function transformLoopMetadata(transform, loopsMeta) {
if (!Array.isArray(loopsMeta)) {
return loopsMeta;
}
return loopsMeta.map((loop) => (
isObject(loop) && Array.isArray(loop.points)
? { ...loop, points: transformPointList(transform, loop.points) }
: loop
));
}
function transformSurface(transform, surface) {
if (!isObject(surface)) {
return surface;
}
const pointKeys = new Set(["origin", "center", "location"]);
const vectorKeys = new Set(["axis", "direction", "normal", "xDir", "yDir"]);
return Object.fromEntries(Object.entries(surface).map(([key, value]) => {
if (pointKeys.has(key) && Array.isArray(value) && value.length === 3) {
return [key, transformPoint(transform, value)];
}
if (vectorKeys.has(key) && Array.isArray(value) && value.length === 3) {
return [key, transformVector(transform, value)];
}
return [key, value];
}));
}
function transformPositions(values, transform) {
if (!(values instanceof Float32Array) || !Array.isArray(transform) || transform.length < 16) {
return values;
}
const next = new Float32Array(values.length);
for (let index = 0; index < values.length; index += 3) {
const point = transformPoint(transform, [values[index], values[index + 1], values[index + 2]]);
next[index] = point[0];
next[index + 1] = point[1];
next[index + 2] = point[2];
}
return next;
}
function transformPickData(pickData, transform) {
if (!isObject(pickData) || !Array.isArray(transform) || transform.length < 16) {
return pickData;
}
return {
...pickData,
bbox: pickData.bbox ? transformBBox(transform, pickData.bbox) : pickData.bbox,
center: Array.isArray(pickData.center) ? transformPoint(transform, pickData.center) : pickData.center,
normal: Array.isArray(pickData.normal) ? transformVector(transform, pickData.normal) : pickData.normal,
params: pickData.params ? transformParams(transform, pickData.params) : pickData.params,
points: transformPointList(transform, pickData.points),
loops: Array.isArray(pickData.loops)
? pickData.loops.map((loop) => transformPointList(transform, loop))
: pickData.loops,
loopsMeta: transformLoopMetadata(transform, pickData.loopsMeta),
surface: transformSurface(transform, pickData.surface),
centroid: Array.isArray(pickData.centroid) ? transformPoint(transform, pickData.centroid) : pickData.centroid,
transform,
};
}
function referenceIdForRow(displaySelector, selectorType, partId) {
if (partId) {
return `topology|${partId}|${selectorType}|${displaySelector}`;
}
return displaySelector;
}
function referenceSummary(selectorType, row) {
if (selectorType === "occurrence") {
return String(row.name || row.sourceName || row.id || "").trim();
}
if (selectorType === "shape") {
const name = String(row.name || row.sourceName || "").trim();
const detail = `${row.kind || "shape"}${row.volume ? ` volume=${row.volume}` : row.area ? ` area=${row.area}` : ""}`;
return name ? `${name} ${detail}` : detail;
}
if (selectorType === "face") {
return `${row.surfaceType || "face"} area=${row.area ?? 0}`;
}
return `${row.curveType || "edge"} length=${row.length ?? 0}`;
}
function sourceOccurrenceMatchesFilter(sourceOccurrenceId, filterOccurrenceId) {
const sourceId = String(sourceOccurrenceId || "").trim();
const filterId = String(filterOccurrenceId || "").trim();
return !filterId || sourceId === filterId || sourceId.startsWith(`${filterId}.`);
}
function remapSourceOccurrenceId(sourceOccurrenceId, remapOccurrencePrefix) {
const sourceId = String(sourceOccurrenceId || "").trim();
if (!sourceId || !remapOccurrencePrefix || typeof remapOccurrencePrefix !== "object") {
return "";
}
if (!sourceOccurrenceMatchesFilter(sourceId, remapOccurrencePrefix.sourceOccurrenceId)) {
return "";
}
const sourceRootId = String(remapOccurrencePrefix.sourceRootOccurrenceId || "").trim();
const targetRootId = String(remapOccurrencePrefix.targetRootOccurrenceId || "").trim();
if (!sourceRootId || !targetRootId) {
return "";
}
if (sourceId === sourceRootId) {
return targetRootId;
}
const sourceRootPrefix = `${sourceRootId}.`;
if (sourceId.startsWith(sourceRootPrefix)) {
return `${targetRootId}.${sourceId.slice(sourceRootPrefix.length)}`;
}
return "";
}
function selectorForRow(selectorType, row, rowIndex, singleOccurrenceId, remapOccurrenceId = "", remapOccurrencePrefix = null) {
if (!row || !Number.isFinite(Number(rowIndex))) {
return "";
}
if (remapOccurrencePrefix && typeof remapOccurrencePrefix === "object") {
const sourceOccurrenceId = selectorType === "occurrence"
? String(row?.id || "").trim()
: String(row?.occurrenceId || "").trim();
const occurrenceId = remapSourceOccurrenceId(sourceOccurrenceId, remapOccurrencePrefix);
if (!occurrenceId) {
return "";
}
if (selectorType === "occurrence") {
return occurrenceId;
}
const selectorKind = selectorType === "shape"
? "s"
: selectorType === "face"
? "f"
: "e";
return `${occurrenceId}.${selectorKind}${rowIndex + 1}`;
}
const occurrenceId = String(remapOccurrenceId || "").trim();
if (occurrenceId) {
if (selectorType === "occurrence") {
return occurrenceId;
}
const selectorKind = selectorType === "shape"
? "s"
: selectorType === "face"
? "f"
: "e";
return `${occurrenceId}.${selectorKind}${rowIndex + 1}`;
}
return selectorPrefix(singleOccurrenceId, String(row?.id || "").trim());
}
function selectorWithRemappedOccurrence(selector, singleOccurrenceId, remapOccurrenceId = "", remapOccurrencePrefix = null) {
const normalizedSelector = String(selector || "").trim();
if (!normalizedSelector) {
return "";
}
const match = normalizedSelector.match(/^(.*)\.([sfe]\d+)$/i);
if (!match) {
return selectorPrefix(singleOccurrenceId, normalizedSelector);
}
const occurrenceId = String(match[1] || "").trim();
const selectorToken = String(match[2] || "").trim();
if (!occurrenceId || !selectorToken) {
return selectorPrefix(singleOccurrenceId, normalizedSelector);
}
if (remapOccurrencePrefix && typeof remapOccurrencePrefix === "object") {
const remappedOccurrenceId = remapSourceOccurrenceId(occurrenceId, remapOccurrencePrefix);
return remappedOccurrenceId ? `${remappedOccurrenceId}.${selectorToken}` : "";
}
const targetOccurrenceId = String(remapOccurrenceId || "").trim();
if (targetOccurrenceId) {
return `${targetOccurrenceId}.${selectorToken}`;
}
return selectorPrefix(singleOccurrenceId, normalizedSelector);
}
function occurrenceIdForReference(selectorType, row, singleOccurrenceId, remapOccurrenceId = "", remapOccurrencePrefix = null) {
if (selectorType === "occurrence") {
return "";
}
const occurrenceId = String(row?.occurrenceId || "").trim();
if (!occurrenceId) {
return "";
}
if (remapOccurrencePrefix && typeof remapOccurrencePrefix === "object") {
return remapSourceOccurrenceId(occurrenceId, remapOccurrencePrefix);
}
return String(remapOccurrenceId || "").trim() || selectorPrefix(singleOccurrenceId, occurrenceId);
}
function buildAdjacencySelectors(row, relationRows, targetRows, singleOccurrenceId, idKey, startKey, countKey, targetSelectorType, remapOccurrenceId, remapOccurrencePrefix) {
const start = Number(row?.[startKey] || 0);
const count = Number(row?.[countKey] || 0);
const selectors = [];
const end = Math.min(relationRows?.length || 0, start + count);
for (let index = start; index < end; index += 1) {
const rowIndex = relationRows[index];
const targetRowIndex = Number(rowIndex);
const targetRow = targetRows[targetRowIndex];
const selector = selectorForRow(
targetSelectorType,
targetRow,
targetRowIndex,
singleOccurrenceId,
remapOccurrenceId,
remapOccurrencePrefix
) || String(targetRow?.[idKey] || "").trim();
if (selector) {
selectors.push(selector);
}
}
return selectors;
}
function buildReference({
selectorType,
row,
rowIndex,
singleOccurrenceId,
copyCadPath,
partId,
selectorTransform,
relationRows,
targetRows,
targetKey,
startKey,
countKey,
remapOccurrenceId = "",
remapOccurrencePrefix = null,
targetSelectorType = "",
}) {
const normalizedSelector = selectorForRow(selectorType, row, rowIndex, singleOccurrenceId, remapOccurrenceId, remapOccurrencePrefix);
const displaySelector = normalizedSelector;
const id = referenceIdForRow(displaySelector, selectorType, partId);
const summary = referenceSummary(selectorType, row);
const copyText = buildCadRefToken({ cadPath: copyCadPath, selector: displaySelector });
const adjacentSelectors = relationRows && targetRows
? buildAdjacencySelectors(
row,
relationRows,
targetRows,
singleOccurrenceId,
targetKey,
startKey,
countKey,
targetSelectorType,
remapOccurrenceId,
remapOccurrencePrefix
)
: [];
return {
id,
selectorType,
normalizedSelector,
displaySelector,
label: `${selectorTypeLabel(selectorType)} ${displaySelector}`,
summary,
shortSummary: summary,
copyText,
partId,
occurrenceId: occurrenceIdForReference(selectorType, row, singleOccurrenceId, remapOccurrenceId, remapOccurrencePrefix),
shapeId: selectorWithRemappedOccurrence(row.shapeId, singleOccurrenceId, remapOccurrenceId, remapOccurrencePrefix),
rowIndex,
pickData: {
selectorType,
rowIndex,
name: row.name || null,
sourceName: row.sourceName || null,
kind: row.kind || null,
bbox: row.bbox || null,
surfaceType: row.surfaceType || null,
center: row.center || null,
normal: row.normal || null,
params: row.params || null,
triangleStart: row.triangleStart ?? 0,
triangleCount: row.triangleCount ?? 0,
segmentStart: row.segmentStart ?? 0,
segmentCount: row.segmentCount ?? 0,
adjacentSelectors,
transform: selectorTransform || null,
},
};
}
function buildLeafOccurrenceIds(shapes) {
return [...new Set(
shapes
.map((row) => String(row.occurrenceId || "").trim())
.filter(Boolean)
)].sort();
}
function transformRows(rows, transform) {
if (!Array.isArray(transform) || transform.length < 16) {
return rows;
}
return rows.map((row) => ({
...row,
transform: Array.isArray(row.transform) ? row.transform : transform,
bbox: row.bbox ? transformBBox(transform, row.bbox) : row.bbox,
center: Array.isArray(row.center) ? transformPoint(transform, row.center) : row.center,
normal: Array.isArray(row.normal) ? transformVector(transform, row.normal) : row.normal,
params: row.params ? transformParams(transform, row.params) : row.params,
}));
}
function transformRow(row, transform) {
if (!row || !Array.isArray(transform) || transform.length < 16) {
return row;
}
return {
...row,
bbox: row.bbox ? transformBBox(transform, row.bbox) : row.bbox,
center: Array.isArray(row.center) ? transformPoint(transform, row.center) : row.center,
normal: Array.isArray(row.normal) ? transformVector(transform, row.normal) : row.normal,
params: row.params ? transformParams(transform, row.params) : row.params,
};
}
function applySequentialRelationStarts(rows, relationSpecs) {
const specs = Array.isArray(relationSpecs?.[0]) ? relationSpecs : [relationSpecs];
const nextStarts = specs.map(() => 0);
return rows.map((row) => {
const nextRow = { ...row };
specs.forEach(([startKey, countKey], specIndex) => {
const count = Math.max(0, Number(row?.[countKey] || 0));
nextRow[startKey] = nextStarts[specIndex];
nextRow[countKey] = count;
nextStarts[specIndex] += count;
});
return nextRow;
});
}
export function buildSelectorRuntime(bundle, {
copyCadPath = "",
partId = "",
transform = null,
remapOccurrenceId = "",
remapOccurrencePrefix = null,
} = {}) {
const manifest = bundle?.manifest || {};
const buffers = bundle?.buffers || {};
const faceRelations = relationArray(manifest, buffers, "faceEdgeRows", "faceEdgeRowsView");
const edgeRelations = relationArray(manifest, buffers, "edgeFaceRows", "edgeFaceRowsView");
const occurrences = transformRows(toRows(manifest, "occurrences", "occurrenceColumns"), transform);
const shapes = transformRows(toRows(manifest, "shapes", "shapeColumns"), transform);
const faces = applySequentialRelationStarts(
transformRows(toRows(manifest, "faces", "faceColumns"), transform),
[["edgeStart", "edgeCount"]]
);
const edges = applySequentialRelationStarts(
transformRows(toRows(manifest, "edges", "edgeColumns"), transform),
[["faceStart", "faceCount"]]
);
const leafOccurrenceIds = buildLeafOccurrenceIds(shapes);
const singleOccurrenceId = leafOccurrenceIds.length === 1 ? leafOccurrenceIds[0] : "";
const selectorBuffers = {
facePositions: transformPositions(buffers.facePositions, transform),
faceIndices: buffers.faceIndices || new Uint32Array(0),
faceIds: buffers.faceIds || new Uint32Array(0),
faceRuns: typedBufferView(manifest, buffers, "faceProxy", "runsView"),
faceRunColumns: Array.isArray(manifest?.faceProxy?.runColumns) ? manifest.faceProxy.runColumns : [],
edgePositions: transformPositions(buffers.edgePositions, transform),
edgeIndices: buffers.edgeIndices || new Uint32Array(0),
edgeIds: buffers.edgeIds || new Uint32Array(0),
faceEdgeRows: faceRelations,
edgeFaceRows: edgeRelations,
};
const references = [];
references.push(...occurrences.map((row, rowIndex) => buildReference({
selectorType: "occurrence",
row,
rowIndex,
singleOccurrenceId,
copyCadPath,
partId,
selectorTransform: transform,
remapOccurrenceId,
remapOccurrencePrefix,
})));
references.push(...shapes.map((row, rowIndex) => buildReference({
selectorType: "shape",
row,
rowIndex,
singleOccurrenceId,
copyCadPath,
partId,
selectorTransform: transform,
remapOccurrenceId,
remapOccurrencePrefix,
})));
references.push(...faces.map((row, rowIndex) => buildReference({
selectorType: "face",
row,
rowIndex,
singleOccurrenceId,
copyCadPath,
partId,
selectorTransform: transform,
relationRows: faceRelations,
targetRows: edges,
targetKey: "id",
startKey: "edgeStart",
countKey: "edgeCount",
remapOccurrenceId,
remapOccurrencePrefix,
targetSelectorType: "edge",
})));
references.push(...edges.map((row, rowIndex) => buildReference({
selectorType: "edge",
row,
rowIndex,
singleOccurrenceId,
copyCadPath,
partId,
selectorTransform: transform,
relationRows: edgeRelations,
targetRows: faces,
targetKey: "id",
startKey: "faceStart",
countKey: "faceCount",
remapOccurrenceId,
remapOccurrencePrefix,
targetSelectorType: "face",
})));
const visibleReferences = references.filter((reference) => String(reference?.normalizedSelector || "").trim());
const referenceMap = new Map(visibleReferences.map((reference) => [reference.id, reference]));
const referenceByNormalizedSelector = new Map(
visibleReferences.map((reference) => [reference.normalizedSelector, reference])
);
const referenceByDisplaySelector = new Map(
visibleReferences.map((reference) => [reference.displaySelector, reference])
);
const faceReferenceByRowIndex = new Map(
visibleReferences
.filter((reference) => reference.selectorType === "face")
.map((reference) => [reference.rowIndex, reference])
);
const edgeReferenceByRowIndex = new Map(
visibleReferences
.filter((reference) => reference.selectorType === "edge")
.map((reference) => [reference.rowIndex, reference])
);
const occurrenceIdByRowIndex = new Map(
occurrences.map((row, rowIndex) => [
rowIndex,
selectorForRow("occurrence", row, rowIndex, singleOccurrenceId, remapOccurrenceId, remapOccurrencePrefix) || String(row?.id || "").trim()
])
);
return {
schemaVersion: Number(manifest.schemaVersion || 1),
surfaceEdgeRendering: Boolean(manifest?.capabilities?.surfaceEdgeRendering),
capabilities: manifest.capabilities || null,
cadPath: copyCadPath || String(manifest.cadRef || "").trim(),
stepHash: String(manifest.stepHash || ""),
bbox: transform ? transformBBox(transform, manifest.bbox || {}) : manifest.bbox,
occurrences,
shapes,
faces,
edges,
vertices: [],
references: visibleReferences,
referenceMap,
referenceByNormalizedSelector,
referenceByDisplaySelector,
faceReferenceByRowIndex,
edgeReferenceByRowIndex,
vertexReferenceByRowIndex: new Map(),
occurrenceIdByRowIndex,
faceReferenceMap: new Map(visibleReferences.filter((reference) => reference.selectorType === "face").map((reference) => [reference.id, reference])),
edgeReferenceMap: new Map(visibleReferences.filter((reference) => reference.selectorType === "edge").map((reference) => [reference.id, reference])),
vertexReferenceMap: new Map(),
singleOccurrenceId,
proxy: selectorBuffers,
};
}
export function buildDisplayEdgeRuntime(bundle, {
transform = null,
} = {}) {
const manifest = bundle?.manifest || {};
if (String(manifest.profile || "") === "surface-edges") {
const edgeRendering = manifest.edgeRendering && typeof manifest.edgeRendering === "object"
? manifest.edgeRendering
: {};
return {
schemaVersion: Number(manifest.schemaVersion || 1),
surfaceEdgeRendering: true,
edgeRendering: {
visibilityClasses: Array.isArray(edgeRendering.visibilityClasses)
? edgeRendering.visibilityClasses.map((item) => String(item || "").trim()).filter(Boolean)
: [],
generatedVisibilityClasses: Array.isArray(edgeRendering.generatedVisibilityClasses)
? edgeRendering.generatedVisibilityClasses.map((item) => String(item || "").trim()).filter(Boolean)
: [],
visibilityClassCounts: edgeRendering.visibilityClassCounts || {},
generatedVisibilityClassCounts: edgeRendering.generatedVisibilityClassCounts || {},
},
stepHash: String(manifest.stepHash || ""),
bbox: transform ? transformBBox(transform, manifest.bbox || {}) : manifest.bbox,
edges: [],
proxy: {
edgePositions: new Float32Array(0),
edgeIndices: new Uint32Array(0),
edgeIds: new Uint32Array(0),
},
};
}
const buffers = bundle?.buffers || {};
const edgeProxy = manifest?.edgeProxy || {};
const edgePositionView = String(edgeProxy.positionsView || "edgePositions");
const edgeIndexView = String(edgeProxy.indicesView || "edgeIndices");
const edgeIdView = String(edgeProxy.edgeIdsView || "edgeIds");
const edgePositions = buffers[edgePositionView] instanceof Float32Array
? buffers[edgePositionView]
: new Float32Array(0);
const edgeIndices = buffers[edgeIndexView] instanceof Uint32Array
? buffers[edgeIndexView]
: new Uint32Array(0);
const edgeIds = buffers[edgeIdView] instanceof Uint32Array
? buffers[edgeIdView]
: new Uint32Array(0);
return {
schemaVersion: Number(manifest.schemaVersion || 1),
stepHash: String(manifest.stepHash || ""),
bbox: transform ? transformBBox(transform, manifest.bbox || {}) : manifest.bbox,
edges: transformRows(toRows(manifest, "edges", "edgeColumns"), transform),
proxy: {
edgePositions: transformPositions(edgePositions, transform),
edgeIndices,
edgeIds,
},
};
}
function normalizedTransformEntries(value) {
const entries = value instanceof Map
? [...value.entries()]
: isObject(value)
? Object.entries(value)
: [];
return entries
.map(([key, transform]) => [
String(key || "").trim(),
Array.isArray(transform) && transform.length >= 16
? transform.slice(0, 16).map((component) => Number(component))
: null
])
.filter(([key, transform]) => key && transform && transform.every(Number.isFinite));
}
function transformForOccurrenceId(transformEntries, occurrenceId) {
const normalizedOccurrenceId = String(occurrenceId || "").trim();
const modelTransform = transformEntries.find(([key]) => key === "__model__" || key === "*" || key === "__all__")?.[1] || null;
let best = null;
for (const [partId, transform] of transformEntries) {
if (partId === "__model__" || partId === "*" || partId === "__all__") {
continue;
}
if (
normalizedOccurrenceId &&
(
normalizedOccurrenceId === partId ||
normalizedOccurrenceId.startsWith(`${partId}.`)
) &&
(!best || partId.length > best[0].length)
) {
best = [partId, transform];
}
}
return best?.[1] || modelTransform;
}
function rowOccurrenceId(row, selectorType) {
return selectorType === "occurrence"
? String(row?.id || "").trim()
: String(row?.occurrenceId || row?.partId || "").trim();
}
function transformRowsByOccurrence(rows, selectorType, transformEntries) {
const transforms = [];
const nextRows = (Array.isArray(rows) ? rows : []).map((row) => {
const transform = transformForOccurrenceId(transformEntries, rowOccurrenceId(row, selectorType));
transforms.push(transform);
return transform ? transformRow(row, transform) : row;
});
return { rows: nextRows, transforms };
}
function transformReference(reference, transform) {
if (!transform) {
return reference;
}
return {
...reference,
pickData: transformPickData(reference.pickData, transform),
};
}
function transformIndexedProxy({ positions, indices, ids, rowTransforms, elementSize }) {
if (!(positions instanceof Float32Array) || !(indices instanceof Uint32Array) || !indices.length || !(ids instanceof Uint32Array) || !ids.length) {
return { positions, indices, ids };
}
const elementCount = Math.floor(indices.length / elementSize);
const nextPositions = new Float32Array(elementCount * elementSize * 3);
const nextIndices = new Uint32Array(elementCount * elementSize);
const nextIds = new Uint32Array(elementCount);
let writeVertex = 0;
for (let elementIndex = 0; elementIndex < elementCount; elementIndex += 1) {
const rowIndex = Number(ids[elementIndex]);
const transform = Number.isInteger(rowIndex) ? rowTransforms[rowIndex] || null : null;
nextIds[elementIndex] = Number.isInteger(rowIndex) ? rowIndex : 0;
for (let vertex = 0; vertex < elementSize; vertex += 1) {
const sourceVertex = Number(indices[(elementIndex * elementSize) + vertex]);
const sourceOffset = sourceVertex * 3;
const nextOffset = writeVertex * 3;
const point = [
positions[sourceOffset],
positions[sourceOffset + 1],
positions[sourceOffset + 2],
];
const transformedPoint = transform ? transformPoint(transform, point) : point;
nextPositions[nextOffset] = transformedPoint[0];
nextPositions[nextOffset + 1] = transformedPoint[1];
nextPositions[nextOffset + 2] = transformedPoint[2];
nextIndices[(elementIndex * elementSize) + vertex] = writeVertex;
writeVertex += 1;
}
}
return {
positions: nextPositions,
indices: nextIndices,
ids: nextIds,
};
}
export function buildTransformedSelectorRuntime(selectorRuntime, transformByPartId = null) {
const transformEntries = normalizedTransformEntries(transformByPartId);
if (!selectorRuntime || !transformEntries.length) {
return selectorRuntime || null;
}
const occurrencesResult = transformRowsByOccurrence(selectorRuntime.occurrences, "occurrence", transformEntries);
const shapesResult = transformRowsByOccurrence(selectorRuntime.shapes, "shape", transformEntries);
const facesResult = transformRowsByOccurrence(selectorRuntime.faces, "face", transformEntries);
const edgesResult = transformRowsByOccurrence(selectorRuntime.edges, "edge", transformEntries);
const referenceTransforms = {
occurrence: occurrencesResult.transforms,
shape: shapesResult.transforms,
face: facesResult.transforms,
edge: edgesResult.transforms,
vertex: [],
};
const references = (Array.isArray(selectorRuntime.references) ? selectorRuntime.references : [])
.map((reference) => transformReference(
reference,
referenceTransforms[reference?.selectorType]?.[Number(reference?.rowIndex)] || null
));
const referenceMap = new Map(references.map((reference) => [reference.id, reference]));
const referenceByNormalizedSelector = new Map(references.map((reference) => [reference.normalizedSelector, reference]));
const referenceByDisplaySelector = new Map(references.map((reference) => [reference.displaySelector, reference]));
const faceReferenceByRowIndex = new Map(
references
.filter((reference) => reference.selectorType === "face")
.map((reference) => [reference.rowIndex, reference])
);
const edgeReferenceByRowIndex = new Map(
references
.filter((reference) => reference.selectorType === "edge")
.map((reference) => [reference.rowIndex, reference])
);
const proxy = selectorRuntime.proxy || {};
const faceProxy = transformIndexedProxy({
positions: proxy.facePositions,
indices: proxy.faceIndices,
ids: proxy.faceIds,
rowTransforms: facesResult.transforms,
elementSize: 3,
});
const edgeProxy = transformIndexedProxy({
positions: proxy.edgePositions,
indices: proxy.edgeIndices,
ids: proxy.edgeIds,
rowTransforms: edgesResult.transforms,
elementSize: 2,
});
return {
...selectorRuntime,
bbox: selectorRuntime.bbox ? transformBBox(transformEntries.find(([key]) => key === "__model__" || key === "*" || key === "__all__")?.[1], selectorRuntime.bbox) : selectorRuntime.bbox,
occurrences: occurrencesResult.rows,
shapes: shapesResult.rows,
faces: facesResult.rows,
edges: edgesResult.rows,
references,
referenceMap,
referenceByNormalizedSelector,
referenceByDisplaySelector,
faceReferenceByRowIndex,
edgeReferenceByRowIndex,
faceReferenceMap: new Map(references.filter((reference) => reference.selectorType === "face").map((reference) => [reference.id, reference])),
edgeReferenceMap: new Map(references.filter((reference) => reference.selectorType === "edge").map((reference) => [reference.id, reference])),
proxy: {
...proxy,
facePositions: faceProxy.positions,
faceIndices: faceProxy.indices,
faceIds: faceProxy.ids,
edgePositions: edgeProxy.positions,
edgeIndices: edgeProxy.indices,
edgeIds: edgeProxy.ids,
},
};
}
export function buildTransformedDisplayEdgeRuntime(displayEdgeRuntime, transformByPartId = null) {
const transformEntries = normalizedTransformEntries(transformByPartId);
if (!displayEdgeRuntime || !transformEntries.length) {
return displayEdgeRuntime || null;
}
const edgesResult = transformRowsByOccurrence(displayEdgeRuntime.edges, "edge", transformEntries);
const proxy = displayEdgeRuntime.proxy || {};
const edgeProxy = transformIndexedProxy({
positions: proxy.edgePositions,
indices: proxy.edgeIndices,
ids: proxy.edgeIds,
rowTransforms: edgesResult.transforms,
elementSize: 2,
});
const modelTransform = transformEntries.find(([key]) => key === "__model__" || key === "*" || key === "__all__")?.[1] || null;
return {
...displayEdgeRuntime,
bbox: modelTransform && displayEdgeRuntime.bbox ? transformBBox(modelTransform, displayEdgeRuntime.bbox) : displayEdgeRuntime.bbox,
edges: edgesResult.rows,
proxy: {
...proxy,
edgePositions: edgeProxy.positions,
edgeIndices: edgeProxy.indices,
edgeIds: edgeProxy.ids,
},
};
}