Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions src/three-components/StepModel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { GLTFExporter } from "three-stdlib"
import { GltfModel } from "./GltfModel"
import type { CadModelFitMode, CadModelSize } from "src/utils/cad-model-fit"
import { getStepModelCacheKey } from "src/utils/step-model-cache-key"

type OcctImportParams = {
linearUnit?: "millimeter" | "centimeter" | "meter" | "inch" | "foot"
Expand Down Expand Up @@ -163,9 +164,9 @@ function base64ToArrayBuffer(base64: string): ArrayBuffer {
return bytes.buffer
}

function getCachedGlb(stepUrl: string): ArrayBuffer | null {
function getCachedGlb(cacheKey: string): ArrayBuffer | null {
try {
const cached = localStorage.getItem(`${CACHE_PREFIX}${stepUrl}`)
const cached = localStorage.getItem(`${CACHE_PREFIX}${cacheKey}`)
if (!cached) {
return null
}
Expand All @@ -176,10 +177,10 @@ function getCachedGlb(stepUrl: string): ArrayBuffer | null {
}
}

function setCachedGlb(stepUrl: string, glb: ArrayBuffer): void {
function setCachedGlb(cacheKey: string, glb: ArrayBuffer): void {
try {
const encoded = arrayBufferToBase64(glb)
localStorage.setItem(`${CACHE_PREFIX}${stepUrl}`, encoded)
localStorage.setItem(`${CACHE_PREFIX}${cacheKey}`, encoded)
} catch (error) {
console.warn("Failed to write STEP GLB cache", error)
}
Expand Down Expand Up @@ -246,15 +247,16 @@ export const StepModel = ({
let objectUrl: string | null = null
let shouldRevokeObjectUrl = true
const registry = getStepUrlConversionRegistry()
const cachedGlb = getCachedGlb(stepUrl)
const stepCacheKey = getStepModelCacheKey(stepUrl)
const cachedGlb = getCachedGlb(stepCacheKey)
if (cachedGlb) {
const cachedConverted: ConvertedStepFile = {
arrayBuffer: cachedGlb,
blobUrl: URL.createObjectURL(
new Blob([cachedGlb], { type: "model/gltf-binary" }),
),
}
registry.completed.set(stepUrl, cachedConverted)
registry.completed.set(stepCacheKey, cachedConverted)
objectUrl = cachedConverted.blobUrl
shouldRevokeObjectUrl = false
setStepGltfUrl(cachedConverted.blobUrl)
Expand All @@ -265,20 +267,20 @@ export const StepModel = ({
}
}
}
const existingCompleted = registry.completed.get(stepUrl)
const existingCompleted = registry.completed.get(stepCacheKey)
if (existingCompleted) {
objectUrl = existingCompleted.blobUrl
shouldRevokeObjectUrl = false
setStepGltfUrl(existingCompleted.blobUrl)
setCachedGlb(stepUrl, existingCompleted.arrayBuffer)
setCachedGlb(stepCacheKey, existingCompleted.arrayBuffer)
return () => {
isActive = false
if (objectUrl && shouldRevokeObjectUrl) {
URL.revokeObjectURL(objectUrl)
}
}
}
let conversionPromise = registry.inProgress.get(stepUrl)
let conversionPromise = registry.inProgress.get(stepCacheKey)
if (!conversionPromise) {
conversionPromise = convertStepUrlToGlb(stepUrl)
.then((glbBuffer) => {
Expand All @@ -288,15 +290,15 @@ export const StepModel = ({
new Blob([glbBuffer], { type: "model/gltf-binary" }),
),
}
registry.completed.set(stepUrl, converted)
registry.inProgress.delete(stepUrl)
registry.completed.set(stepCacheKey, converted)
registry.inProgress.delete(stepCacheKey)
return converted
})
.catch((error) => {
registry.inProgress.delete(stepUrl)
registry.inProgress.delete(stepCacheKey)
throw error
})
registry.inProgress.set(stepUrl, conversionPromise)
registry.inProgress.set(stepCacheKey, conversionPromise)
}
void conversionPromise
.then((converted) => {
Expand All @@ -306,7 +308,7 @@ export const StepModel = ({
objectUrl = converted.blobUrl
shouldRevokeObjectUrl = false
setStepGltfUrl(converted.blobUrl)
setCachedGlb(stepUrl, converted.arrayBuffer)
setCachedGlb(stepCacheKey, converted.arrayBuffer)
})
.catch((error) => {
console.error("Failed to convert STEP file to GLB", error)
Expand Down
24 changes: 24 additions & 0 deletions src/utils/step-model-cache-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export function getStepModelCacheKey(stepUrl: string): string {
const hashIndex = stepUrl.indexOf("#")
const urlWithoutHash =
hashIndex === -1 ? stepUrl : stepUrl.slice(0, hashIndex)
const hash = hashIndex === -1 ? "" : stepUrl.slice(hashIndex)
const queryIndex = urlWithoutHash.indexOf("?")

if (queryIndex === -1) {
return stepUrl
}

const path = urlWithoutHash.slice(0, queryIndex)
const query = urlWithoutHash.slice(queryIndex + 1)
const filteredQuery = query
.split("&")
.filter((part) => part.split("=", 1)[0] !== "cachebust_origin")
.join("&")

if (!filteredQuery) {
return `${path}${hash}`
}

return `${path}?${filteredQuery}${hash}`
}
35 changes: 35 additions & 0 deletions tests/step-model-cache-key.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { expect, test } from "bun:test"
import { getStepModelCacheKey } from "../src/utils/step-model-cache-key"

test("normalizes empty cachebust_origin query params for STEP cache keys", () => {
expect(
getStepModelCacheKey(
"https://modelcdn.tscircuit.com/easyeda_models/download?uuid=abc&pn=C1&cachebust_origin=",
),
).toBe(
"https://modelcdn.tscircuit.com/easyeda_models/download?uuid=abc&pn=C1",
)
})

test("normalizes cachebust_origin wherever it appears in the query", () => {
expect(
getStepModelCacheKey(
"/models/chip.step?cachebust_origin=preview-1&uuid=abc&pn=C1#part",
),
).toBe("/models/chip.step?uuid=abc&pn=C1#part")

expect(
getStepModelCacheKey(
"/models/chip.step?uuid=abc&cachebust_origin=preview-2&pn=C1",
),
).toBe("/models/chip.step?uuid=abc&pn=C1")
})

test("preserves meaningful query params and hashes for distinct STEP URLs", () => {
expect(getStepModelCacheKey("/models/chip.step?variant=A#pins")).toBe(
"/models/chip.step?variant=A#pins",
)
expect(getStepModelCacheKey("/models/chip.step#pins")).toBe(
"/models/chip.step#pins",
)
})
Loading