type Vector3 = [number, number, number]; type SidecarReference = { id?: string; selectorType?: string; label?: string; center?: unknown; normal?: unknown; frame?: Record; bbox?: unknown; surface_type?: string; triangle_start?: number; triangle_count?: number; }; type Sidecar = { references?: SidecarReference[] }; const GLB_CAD_UNIT_SCALE = 1000; const FACE_ID_NONE = 0xffffffff; function vector(value: unknown): Vector3 | null { if (!Array.isArray(value) || value.length < 3) return null; const result = value.slice(0, 3).map(Number) as Vector3; return result.every(Number.isFinite) ? result : null; } // The local preview runtime converts build123d's Y-up GLB positions into this // CAD coordinate convention. Sidecar coordinates remain native CDSL mm. function previewVector(value: Vector3): Vector3 { return [value[0] * GLB_CAD_UNIT_SCALE, -value[2] * GLB_CAD_UNIT_SCALE, value[1] * GLB_CAD_UNIT_SCALE]; } function normalized(value: Vector3): Vector3 | null { const length = Math.hypot(value[0], value[1], value[2]); return length > 1e-9 ? [value[0] / length, value[1] / length, value[2] / length] : null; } function dot(left: Vector3, right: Vector3) { return left[0] * right[0] + left[1] * right[1] + left[2] * right[2]; } function subtract(left: Vector3, right: Vector3): Vector3 { return [left[0] - right[0], left[1] - right[1], left[2] - right[2]]; } function cross(left: Vector3, right: Vector3): Vector3 { return [ left[1] * right[2] - left[2] * right[1], left[2] * right[0] - left[0] * right[2], left[0] * right[1] - left[1] * right[0], ]; } function mappedBBox(value: unknown) { const bbox = value as { min?: unknown; max?: unknown } | null; const min = vector(bbox?.min); const max = vector(bbox?.max); if (!min || !max) return null; const points: Vector3[] = []; for (const x of [min[0], max[0]]) { for (const y of [min[1], max[1]]) { for (const z of [min[2], max[2]]) points.push(previewVector([x, y, z])); } } return { min: [Math.min(...points.map((point) => point[0])), Math.min(...points.map((point) => point[1])), Math.min(...points.map((point) => point[2]))], max: [Math.max(...points.map((point) => point[0])), Math.max(...points.map((point) => point[1])), Math.max(...points.map((point) => point[2]))], }; } function sourceFrame(reference: SidecarReference) { const frame = reference.frame || {}; const origin = vector(frame.origin_mm) || vector(reference.center); const normal = vector(frame.normal) || vector(reference.normal); const xDir = vector(frame.x_dir) || [1, 0, 0] as Vector3; const yDir = vector(frame.y_dir) || [0, 1, 0] as Vector3; return origin && normal ? { origin_mm: origin, normal, x_dir: xDir, y_dir: yDir } : null; } function triangleRuns(faceIds: Uint32Array, occurrenceRow: number) { const rows: number[] = []; let index = 0; while (index < faceIds.length) { const faceRow = faceIds[index]; if (faceRow === FACE_ID_NONE) { index += 1; continue; } const start = index; while (index < faceIds.length && faceIds[index] === faceRow) index += 1; rows.push(occurrenceRow, 0, start, index - start, faceRow); } return new Uint32Array(rows); } export function buildCdslSelectorRuntime(sidecar: Sidecar, meshData: any) { const sourceReferences = Array.isArray(sidecar?.references) ? sidecar.references : []; const faces = sourceReferences .filter((reference) => String(reference?.selectorType || "").toLowerCase() === "face") .map((reference, rowIndex) => { const frame = sourceFrame(reference); if (!frame) return null; const previewCenter = previewVector(frame.origin_mm); const previewNormal = normalized(previewVector(frame.normal)); if (!previewNormal) return null; const displaySelector = String(reference.id || `face_${rowIndex}`); return { id: displaySelector, selectorType: "face", normalizedSelector: displaySelector, displaySelector, label: String(reference.label || displaySelector), summary: String(reference.label || displaySelector), shortSummary: String(reference.label || displaySelector), partId: "glb:0", rowIndex, pickData: { selectorType: "face", rowIndex, bbox: mappedBBox(reference.bbox), center: previewCenter, normal: previewNormal, surfaceType: String(reference.surface_type || "plane"), surface: { type: String(reference.surface_type || "plane"), origin: previewCenter, normal: previewNormal, xDir: normalized(previewVector(frame.x_dir)) || [1, 0, 0], yDir: normalized(previewVector(frame.y_dir)) || [0, 1, 0], }, cdslFrame: frame, cdslCoordinateSystem: "build123d_y_up_glb", // Keep native CDSL millimetres for the deterministic edit compiler. frame, triangleStart: Math.max(0, Math.floor(Number(reference.triangle_start) || 0)), triangleCount: Math.max(0, Math.floor(Number(reference.triangle_count) || 0)), adjacentSelectors: [], }, }; }) .filter(Boolean) as any[]; const vertices = meshData?.vertices; const indices = meshData?.indices; const triangleCount = Math.floor((indices?.length || 0) / 3); const faceIds = new Uint32Array(triangleCount); faceIds.fill(FACE_ID_NONE); const explicitRanges = faces.some((face) => face.pickData.triangleCount > 0); if (explicitRanges) { for (const face of faces) { const start = Math.min(face.pickData.triangleStart, triangleCount); const end = Math.min(start + face.pickData.triangleCount, triangleCount); faceIds.fill(face.rowIndex, start, end); } } else if (vertices && indices) { const planeTolerance = 0.8 * GLB_CAD_UNIT_SCALE; for (let triangleIndex = 0; triangleIndex < triangleCount; triangleIndex += 1) { const positions = [0, 1, 2].map((corner) => { const offset = Number(indices[(triangleIndex * 3) + corner]) * 3; return [Number(vertices[offset]), Number(vertices[offset + 1]), Number(vertices[offset + 2])] as Vector3; }); if (!positions.flat().every(Number.isFinite)) continue; const center: Vector3 = [ (positions[0][0] + positions[1][0] + positions[2][0]) / 3, (positions[0][1] + positions[1][1] + positions[2][1]) / 3, (positions[0][2] + positions[1][2] + positions[2][2]) / 3, ]; const triangleNormal = normalized(cross(subtract(positions[1], positions[0]), subtract(positions[2], positions[0]))); let bestRow = FACE_ID_NONE; let bestScore = Infinity; for (const face of faces) { const normal = face.pickData.normal as Vector3; const distance = Math.abs(dot(subtract(center, face.pickData.center as Vector3), normal)); if (distance > planeTolerance) continue; const alignment = triangleNormal ? Math.abs(dot(triangleNormal, normal)) : 1; if (alignment < 0.7) continue; const score = distance + ((1 - alignment) * planeTolerance); if (score < bestScore) { bestScore = score; bestRow = face.rowIndex; } } faceIds[triangleIndex] = bestRow; } } const faceRuns = triangleRuns(faceIds, 0); const references = faces; return { schemaVersion: 1, surfaceEdgeRendering: false, capabilities: null, cadPath: "", stepHash: "", bbox: meshData?.bounds || null, occurrences: [{ id: "glb:0" }], shapes: [], faces: references, edges: [], vertices: [], references, referenceMap: new Map(references.map((reference) => [reference.id, reference])), referenceByNormalizedSelector: new Map(references.map((reference) => [reference.normalizedSelector, reference])), referenceByDisplaySelector: new Map(references.map((reference) => [reference.displaySelector, reference])), faceReferenceByRowIndex: new Map(references.map((reference) => [reference.rowIndex, reference])), edgeReferenceByRowIndex: new Map(), vertexReferenceByRowIndex: new Map(), occurrenceIdByRowIndex: new Map([[0, "glb:0"]]), faceReferenceMap: new Map(references.map((reference) => [reference.id, reference])), edgeReferenceMap: new Map(), vertexReferenceMap: new Map(), singleOccurrenceId: "glb:0", proxy: { facePositions: new Float32Array(0), faceIndices: new Uint32Array(0), faceIds, faceRuns, faceRunColumns: ["occurrenceRow", "primitiveIndex", "triangleStart", "triangleCount", "faceRow"], edgePositions: new Float32Array(0), edgeIndices: new Uint32Array(0), edgeIds: new Uint32Array(0), faceEdgeRows: [], edgeFaceRows: [], }, }; }