894 lines
32 KiB
JavaScript
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,
|
|
},
|
|
};
|
|
}
|