aa17a48533
* fix(viewer-lib): Transformed is no longer baked in if matrix is identity * fix(viewer-lib): Do not use uint32 indices unless we have to * fix(viewer-lib): Do not use Float64 array unless the batch needs RTE * feat(viewer-lib): Update on reducing memory allocation during startup: - Geometry data is now stored as separate DataChunks as they come - Dechunking now no longer allocates memory. It just returns the DataChunk array - Updated the SpeckleGeometryConverter to work with chunk arrays - Updated Geometry and triangulation to work with chunk arrays - All geometry type batches now work with chunk arrays instead of flattened arrays - Chunks are tracked by use and deleted after all render views are done with them. The chunks also track their usage across different render views so they aren't deleted until all render views that use tham are finished with them - In order to better support this new way of working with geometry data, VirtualArray and ChunkArray classes have been implemented. They make it easier to work with segmented data and offer a unified view on the array of array segments * chore(viewer-lib): Denormalized normals to keep parity, even though they should be normalized * feat(viewer-lib): Geometry transformation is now deferred until we build batches, and we transform the batched arrays leaving the original data chunks intact. Text and TextBatchObject now use the render view's 'transform' property to store it's final startup transformation and not 'bakeTransform' anymore * fix(viewer-lib): Fixed the issues caused by chunking geometry to the acceleration structures. * chore(viewer-lib): Made a pass on the entire viewer project and removed pointless typed array backing buffer re-allocations * feat(viewer-lib): Updates on better large model support: - Fixed an issue in LineBatach that broke building it - Improved VirtualArray performance and added some extra functionality - Already triangulated faces no longer allocate redundant memory, they get processed in place - Moved triangulation to SpeckleConverter so that processed index chunks get stored in local storage so we don't have to re-triangulate each time * feat(viewer-lib): Gave up on trying to cache triangulated indice. Too much hasle and edge cases to handle when only some chunks get saved as triangulated in a multi chunk setup * fix(viewer-lib): Fixed non triangulted geometry converter return * feat(viewer-lib): Glow-up to our triangulation implementation. Faster, zero allocation * fix(viewer-lib): Frontfacing not backfacing triangles * chore(viewer-lib): Fixed compile errors * fix(viewer-lib): Already processed chunks just copy over * fix(viewer-lib): Skip processed chunks when computing triangulation index size * fix(viewer-lib): Some fixes: - Fixed an issue where instances that will not be rendered as instanced geometry were not correctly transformed - Removed geometry duplication from instances that were de-instanced in the batcher - Fixed an issue with LineBatch and buffer type * fix(viewer-lib): Implemented box3 bounds generation from ChunkArray which takes care to respect inter-chunk bounds for vec3. Without this, box3s were incorrectly calculated by computing a box3 for each chunk * fix(viewer-lib): Fixed an issue where transformations that contain non-uniform scaling incorrectly produce node render views aabb values. So we recompute them based on the post-transform geometry when building batches * fix(viewer-lib): When mixing triangles with ngons we also need to increment total tris count for the triangle case as well * fix(viewer-lib): If geometry is invalid, clear it all * fix(viewer-lib): Instanced rvs no longer transform their aabbs when building the render tree because they don't need to * fix(viewer-lib): aabb for render views needs to be recomputed when de-instanced by the batcher
330 lines
9.0 KiB
TypeScript
330 lines
9.0 KiB
TypeScript
import { SpeckleViewer } from '@speckle/shared'
|
|
import Logger from './utils/Logger.js'
|
|
|
|
interface ReferencedObjectUrl {
|
|
origin: string
|
|
projectId: string
|
|
}
|
|
|
|
interface CommitReferencedObjectUrl {
|
|
origin: string
|
|
streamId: string
|
|
commitId: string
|
|
}
|
|
|
|
export async function getResourceUrls(
|
|
url: string,
|
|
authToken?: string
|
|
): Promise<string[]> {
|
|
/** I'm up for a better way of doing this */
|
|
if (url.includes('streams')) return getOldResourceUrls(url, authToken)
|
|
return getNewResourceUrls(url, authToken)
|
|
}
|
|
|
|
async function getOldResourceUrls(url: string, authToken?: string): Promise<string[]> {
|
|
const parsed = new URL(url)
|
|
const streamId = url.split('/streams/')[1].substring(0, 10)
|
|
|
|
const objsUrls = []
|
|
// supports commit based urls
|
|
if (url.includes('commits')) {
|
|
const commitId = url.split('/commits/')[1].substring(0, 10)
|
|
const objUrl = await getCommitReferencedObjectUrl(
|
|
{
|
|
origin: parsed.origin,
|
|
streamId,
|
|
commitId
|
|
},
|
|
authToken
|
|
)
|
|
objsUrls.push(objUrl)
|
|
}
|
|
|
|
// object based urls
|
|
if (url.includes('objects')) objsUrls.push(url)
|
|
|
|
// supports urls that include overlay queries
|
|
// e.g., https://speckle.xyz/streams/a632e7a784/objects/457c45feffa6f954572e5e86fb6d4f25?overlay=cf8dc76247,f5adc1d991b3dceb4b5ad6b50f919a0e
|
|
if (url.includes('overlay=')) {
|
|
const searchParams = new URLSearchParams(parsed.search)
|
|
const resIds = searchParams.get('overlay')?.split(',')
|
|
if (resIds !== undefined) {
|
|
for (const resId of resIds) {
|
|
if (resId.length === 10) {
|
|
objsUrls.push(
|
|
await getCommitReferencedObjectUrl(
|
|
{
|
|
origin: parsed.origin,
|
|
streamId,
|
|
commitId: resId
|
|
} as CommitReferencedObjectUrl,
|
|
authToken
|
|
)
|
|
)
|
|
} else {
|
|
objsUrls.push(`${parsed.origin}/streams/${streamId}/objects/${resId}`)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return objsUrls
|
|
}
|
|
|
|
async function getCommitReferencedObjectUrl(
|
|
ref: CommitReferencedObjectUrl,
|
|
authToken?: string
|
|
) {
|
|
const headers: { 'Content-Type': string; Authorization: string } = {
|
|
'Content-Type': 'application/json',
|
|
Authorization: ''
|
|
}
|
|
if (authToken) {
|
|
headers['Authorization'] = `Bearer ${authToken}`
|
|
}
|
|
const res = await fetch(`${ref.origin}/graphql`, {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify({
|
|
query: `
|
|
query Stream($streamId: String!, $commitId: String!) {
|
|
stream(id: $streamId) {
|
|
commit(id: $commitId) {
|
|
referencedObject
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: { streamId: ref.streamId, commitId: ref.commitId }
|
|
})
|
|
})
|
|
|
|
const { data } = await res.json()
|
|
return `${ref.origin}/streams/${ref.streamId}/objects/${data.stream.commit.referencedObject}`
|
|
}
|
|
|
|
async function getNewResourceUrls(url: string, authToken?: string): Promise<string[]> {
|
|
const parsed = new URL(decodeURI(url))
|
|
const params = parsed.href.match(/[^/]+$/)
|
|
if (!params) {
|
|
return Promise.reject(new Error('No model or object ids specified'))
|
|
}
|
|
|
|
const projectId = parsed.href.split('/projects/')[1].substring(0, 10)
|
|
const headers: { 'Content-Type': string; Authorization: string } = {
|
|
'Content-Type': 'application/json',
|
|
Authorization: ''
|
|
}
|
|
|
|
if (authToken) {
|
|
headers['Authorization'] = `Bearer ${authToken}`
|
|
}
|
|
|
|
const ref: ReferencedObjectUrl = {
|
|
origin: parsed.origin,
|
|
projectId
|
|
}
|
|
|
|
const resources = SpeckleViewer.ViewerRoute.parseUrlParameters(
|
|
decodeURIComponent(params[0])
|
|
)
|
|
|
|
const promises = []
|
|
for (let k = 0; k < resources.length; k++) {
|
|
const resource: SpeckleViewer.ViewerRoute.ViewerResource = resources[k]
|
|
|
|
if (SpeckleViewer.ViewerRoute.isObjectResource(resource)) {
|
|
promises.push(objectResourceToUrl(ref, resource))
|
|
} else if (SpeckleViewer.ViewerRoute.isModelResource(resource)) {
|
|
promises.push(modelResourceToUrl(headers, ref, resource))
|
|
} else if (SpeckleViewer.ViewerRoute.isAllModelsResource(resource)) {
|
|
promises.push(modelAllResourceToUrl(headers, ref))
|
|
}
|
|
}
|
|
|
|
try {
|
|
const results = await Promise.all(promises)
|
|
return results.flatMap((val) => (Array.isArray(val) ? val : [val]))
|
|
} catch (e) {
|
|
Logger.error(e)
|
|
return []
|
|
}
|
|
}
|
|
|
|
async function objectResourceToUrl(
|
|
ref: ReferencedObjectUrl,
|
|
resource: SpeckleViewer.ViewerRoute.ViewerObjectResource
|
|
): Promise<string> {
|
|
return Promise.resolve(
|
|
`${ref.origin}/streams/${ref.projectId}/objects/${resource.toString()}`
|
|
)
|
|
}
|
|
|
|
async function modelResourceToUrl(
|
|
headers: {
|
|
'Content-Type': string
|
|
Authorization: string
|
|
},
|
|
ref: ReferencedObjectUrl,
|
|
resource: SpeckleViewer.ViewerRoute.ViewerModelResource
|
|
): Promise<string> {
|
|
return resource.versionId
|
|
? runModelVersionQuery(headers, ref, resource)
|
|
: runModelLastVersionQuery(headers, ref, resource)
|
|
}
|
|
|
|
async function modelAllResourceToUrl(
|
|
headers: {
|
|
'Content-Type': string
|
|
Authorization: string
|
|
},
|
|
ref: ReferencedObjectUrl
|
|
): Promise<string[]> {
|
|
return runAllModelsQuery(headers, ref)
|
|
}
|
|
|
|
async function runModelLastVersionQuery(
|
|
headers: { 'Content-Type': string; Authorization: string },
|
|
ref: ReferencedObjectUrl,
|
|
resource: SpeckleViewer.ViewerRoute.ViewerModelResource
|
|
): Promise<string> {
|
|
const res = await fetch(`${ref.origin}/graphql`, {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify({
|
|
query: `
|
|
query ViewerUrlHelperModelLastVersion($modelId: String!, $projectId: String!) {
|
|
project(id: $projectId) {
|
|
model(id: $modelId) {
|
|
versions(limit: 1) {
|
|
items {
|
|
referencedObject
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
projectId: ref.projectId,
|
|
modelId: resource.modelId
|
|
}
|
|
})
|
|
})
|
|
try {
|
|
const data = await getResponse(res)
|
|
return `${ref.origin}/streams/${ref.projectId}/objects/${data.project.model.versions.items[0].referencedObject}`
|
|
} catch (e: unknown) {
|
|
return Promise.reject(
|
|
new Error(
|
|
`Could not get object URLs for project ${ref.projectId} and model ${
|
|
resource.modelId
|
|
}. Error: ${e instanceof Error ? e.message : e}`
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
async function runModelVersionQuery(
|
|
headers: { 'Content-Type': string; Authorization: string },
|
|
ref: ReferencedObjectUrl,
|
|
resource: SpeckleViewer.ViewerRoute.ViewerModelResource
|
|
): Promise<string> {
|
|
const res = await fetch(`${ref.origin}/graphql`, {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify({
|
|
query: `
|
|
query ViewerUrlHelperModelVersion($modelId: String!, $projectId: String!, $versionId: String!) {
|
|
project(id: $projectId) {
|
|
model(id: $modelId) {
|
|
version(id: $versionId) {
|
|
referencedObject
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
projectId: ref.projectId,
|
|
modelId: resource.modelId,
|
|
versionId: resource.versionId
|
|
}
|
|
})
|
|
})
|
|
try {
|
|
const data = await getResponse(res)
|
|
return `${ref.origin}/streams/${ref.projectId}/objects/${data.project.model.version.referencedObject}`
|
|
} catch (e) {
|
|
return Promise.reject(
|
|
new Error(
|
|
`Could not get object URLs for project ${ref.projectId} and model ${
|
|
resource.modelId
|
|
}. Error: ${e instanceof Error ? e.message : e}`
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
async function runAllModelsQuery(
|
|
headers: { 'Content-Type': string; Authorization: string },
|
|
ref: ReferencedObjectUrl
|
|
): Promise<string[]> {
|
|
const res = await fetch(`${ref.origin}/graphql`, {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify({
|
|
query: `
|
|
query ViewerUrlHelperAllModel($projectId: String!) {
|
|
project(id: $projectId) {
|
|
models {
|
|
items {
|
|
versions(limit: 1) {
|
|
items {
|
|
referencedObject
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
projectId: ref.projectId
|
|
}
|
|
})
|
|
})
|
|
try {
|
|
const data = await getResponse(res)
|
|
const urls: string[] = []
|
|
data.project.models.items.forEach(
|
|
(element: { versions: { items: { referencedObject: string }[] } }) => {
|
|
if (element.versions.items.length)
|
|
urls.push(
|
|
`${ref.origin}/streams/${ref.projectId}/objects/${element.versions.items[0].referencedObject}`
|
|
)
|
|
}
|
|
)
|
|
return urls
|
|
} catch (e) {
|
|
return Promise.reject(
|
|
new Error(
|
|
`Could not get object URLs for project ${ref.projectId}. Error: ${
|
|
e instanceof Error ? e.message : e
|
|
}`
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
async function getResponse(res: Response) {
|
|
const { data } = await res.json()
|
|
if (!data) throw new Error(`Query failed`)
|
|
|
|
if (!data.project) throw new Error('Project not found')
|
|
|
|
if (!data.project.model && !data.project.models) throw new Error('Model(s) not found')
|
|
|
|
return data
|
|
}
|