@shapediver/sdk.sdtf-v1
Version:
Official sdTF SDK for TypeScript
284 lines (265 loc) • 12.7 kB
text/typescript
import {
isDataObject,
ISdtfReadableContentComponent,
ISdtfWriteableAttribute,
SdtfError,
} from '@shapediver/sdk.sdtf-core';
import { ISdtfReadableComponentList } from '../reader/ISdtfReadableComponentList';
import { ISdtfComponentValidator } from '../validation/ISdtfComponentValidator';
import { SdtfComponentValidator } from '../validation/SdtfComponentValidator';
import { ISdtfWriteableComponentList } from '../writer/ISdtfWriteableComponentList';
import { ISdtfComponentFactory } from './ISdtfComponentFactory';
import { ISdtfComponentList, ISdtfPartialComponentList } from './ISdtfComponentList';
import { SdtfComponentFactory } from './SdtfComponentFactory';
export class SdtfComponentFactoryWrapper implements SdtfComponentFactoryWrapper {
private readonly factory: ISdtfComponentFactory;
constructor() {
this.factory = new SdtfComponentFactory();
}
createFromJson(json: Record<string, unknown>): ISdtfComponentList {
const f = this.factory; // Alias to shorten lines
return this.createComponentList({
accessors: this.buildFromArray(
json,
f.propertyNameAccessors,
f.createAccessor.bind(f)
),
asset: this.buildFromObject(json, '', f.createAsset.bind(f)),
attributes: this.buildFromArray(
json,
f.propertyNameAttributes,
f.createAttributes.bind(f)
),
buffers: this.buildFromArray(json, f.propertyNameBuffers, f.createBuffer.bind(f)),
bufferViews: this.buildFromArray(
json,
f.propertyNameBufferViews,
f.createBufferView.bind(f)
),
chunks: this.buildFromArray(json, f.propertyNameChunks, f.createChunk.bind(f)),
items: this.buildFromArray(json, f.propertyNameDataItems, f.createDataItem.bind(f)),
fileInfo: this.buildFromObject(json, f.propertyNameFileInfo, f.createFileInfo.bind(f)),
nodes: this.buildFromArray(json, f.propertyNameNodes, f.createNode.bind(f)),
typeHints: this.buildFromArray(
json,
f.propertyNameTypeHints,
f.createTypeHint.bind(f)
),
});
}
createFromReadable(readableComponents: ISdtfReadableComponentList): ISdtfComponentList {
const f = this.factory; // Alias to shorten lines
const partialComponentList: ISdtfPartialComponentList = {
accessors: readableComponents.accessors.map((a) => f.createAccessor(a.toDataObject())),
asset: f.createAsset(readableComponents.asset.toDataObject()),
attributes: readableComponents.attributes.map((a) =>
f.createAttributes(a.toDataObject())
),
buffers: readableComponents.buffers.map((b) => f.createBuffer(b.toDataObject())),
bufferViews: readableComponents.bufferViews.map((b) =>
f.createBufferView(b.toDataObject())
),
chunks: readableComponents.chunks.map((c) => f.createChunk(c.toDataObject())),
items: readableComponents.items.map((i) => f.createDataItem(i.toDataObject())),
fileInfo: f.createFileInfo(readableComponents.fileInfo.toDataObject()),
nodes: readableComponents.nodes.map((n) => f.createNode(n.toDataObject())),
typeHints: readableComponents.typeHints.map((y) => f.createTypeHint(y.toDataObject())),
};
this.mapHierarchyRepresentation(partialComponentList, readableComponents);
return this.createComponentList(partialComponentList);
}
createFromWriteable(writeableComponents: ISdtfWriteableComponentList): ISdtfComponentList {
const f = this.factory; // Alias to shorten lines
const partialComponentList: ISdtfPartialComponentList = {
accessors: writeableComponents.accessors.map((a) =>
f.createAccessor(a.toDataObject())
),
asset: f.createAsset(writeableComponents.asset.toDataObject()),
attributes: writeableComponents.attributes.map((a) =>
f.createAttributes(a.toDataObject())
),
buffers: writeableComponents.buffers.map((b) => f.createBuffer(b.toDataObject())),
bufferViews: writeableComponents.bufferViews.map((b) =>
f.createBufferView(b.toDataObject())
),
chunks: writeableComponents.chunks.map((c) => f.createChunk(c.toDataObject())),
items: writeableComponents.items.map((i) => f.createDataItem(i.toDataObject())),
fileInfo: f.createFileInfo(writeableComponents.fileInfo.toDataObject()),
nodes: writeableComponents.nodes.map((n) => f.createNode(n.toDataObject())),
typeHints: writeableComponents.typeHints.map((t) =>
f.createTypeHint(t.toDataObject())
),
// The writeable-optimizer merges all buffers, thus there is only a single binary buffer
binaryBody: writeableComponents.buffers.find((buffer) => !buffer.uri)?.data,
};
this.mapHierarchyRepresentation(partialComponentList, writeableComponents);
return this.createComponentList(partialComponentList);
}
/**
* Validates every partial-component and returns a corresponding component list.
* @private
* @throws {@link SdtfError} when a partial-component is invalid.
*/
createComponentList(partialComponents: ISdtfPartialComponentList): ISdtfComponentList {
const validator: ISdtfComponentValidator = new SdtfComponentValidator(partialComponents);
// NOTE the validation order is important here!
const asset = partialComponents.asset;
validator.validateAsset(asset);
const fileInfo = partialComponents.fileInfo;
validator.validateFileInfo(fileInfo);
const buffers = partialComponents.buffers.map((b) => {
validator.validateBuffer(b);
return b;
});
const bufferViews = partialComponents.bufferViews.map((b) => {
validator.validateBufferView(b);
return b;
});
const accessors = partialComponents.accessors.map((a) => {
validator.validateAccessor(a);
return a;
});
const typeHints = partialComponents.typeHints.map((t) => {
validator.validateTypeHint(t);
return t;
});
const attributes = partialComponents.attributes.map((a) => {
validator.validateAttributes(a);
return a;
});
const chunks = partialComponents.chunks.map((c) => {
validator.validateChunk(c);
return c;
});
const dataItems = partialComponents.items.map((d) => {
validator.validateDataItem(d);
return d;
});
const nodes = partialComponents.nodes.map((n) => {
validator.validateNode(n);
return n;
});
return {
accessors,
asset,
attributes,
buffers,
bufferViews,
chunks,
items: dataItems,
nodes,
typeHints,
fileInfo,
binaryBody: partialComponents.binaryBody,
};
}
/**
* Validation wrapper around the given create function for a content object of the specified name.
* @private
* @throws {@link SdtfError} when something goes wrong.
*/
buildFromObject<T>(
jsonObject: Record<string, unknown>,
propertyName: string,
createFn: (data: Record<string, unknown>) => T
): T {
const componentDataObject = propertyName ? jsonObject[propertyName] : jsonObject;
if (!isDataObject(componentDataObject))
throw new SdtfError(`Invalid item at ${propertyName}: Item must be an object.`);
return createFn(componentDataObject);
}
/**
* Validation wrapper around the given create function for a content array of the specified name.
* @private
* @throws {@link SdtfError} when something goes wrong.
*/
buildFromArray<T>(
jsonArray: Record<string, unknown>,
propertyName: string,
createFn: (data: Record<string, unknown>) => T
): T[] {
const componentDataArray = jsonArray[propertyName];
if (componentDataArray === undefined) return [];
if (!Array.isArray(componentDataArray))
throw new SdtfError(`Invalid content property: '${propertyName}' must be an array.`);
return componentDataArray.map((componentDataItem: unknown, i) => {
if (!isDataObject(componentDataItem))
throw new SdtfError(
`Invalid item at ${propertyName}[${i}]: Item must be an object.`
);
return createFn(componentDataItem);
});
}
/**
* Readable and writeable components use object references to represent the hierarchy.
* However, sdTF components use reference IDs for this.
* This method maps the object representation into a reference ID representation.
* @private
*/
mapHierarchyRepresentation(
target: ISdtfPartialComponentList,
src: ISdtfWriteableComponentList | ISdtfReadableComponentList
): void {
// Helper to find the element position in an array
const getIndex = (list: { componentId: string }[], componentId?: string) => {
if (!componentId) return -1;
return list.findIndex((c) => c.componentId === componentId);
};
target.asset.fileInfo = 0;
target.accessors.forEach((accessor, index) => {
const srcComponent = src.accessors[index];
accessor.bufferView = getIndex(src.bufferViews, srcComponent?.bufferView?.componentId);
});
target.attributes.forEach((attributes, index) => {
if (!attributes.entries) return;
Object.entries(attributes.entries).forEach(([name, attribute]) => {
const srcComponent: ISdtfWriteableAttribute | ISdtfReadableContentComponent =
src.attributes[index].entries[name];
if (srcComponent.accessor)
attribute.accessor = getIndex(
src.accessors,
srcComponent?.accessor?.componentId
);
if (srcComponent.typeHint)
attribute.typeHint = getIndex(
src.typeHints,
srcComponent?.typeHint?.componentId
);
});
});
target.bufferViews.forEach((bufferView, index) => {
const srcComponent = src.bufferViews[index];
bufferView.buffer = getIndex(src.buffers, srcComponent?.buffer?.componentId);
});
target.chunks.forEach((chunk, index) => {
const srcComponent = src.chunks[index];
if (srcComponent.attributes)
chunk.attributes = getIndex(src.attributes, srcComponent?.attributes?.componentId);
chunk.items = srcComponent.items.map((item) => getIndex(src.items, item?.componentId));
chunk.nodes = srcComponent.nodes.map((node) => getIndex(src.nodes, node?.componentId));
if (srcComponent.typeHint)
chunk.typeHint = getIndex(src.typeHints, srcComponent?.typeHint?.componentId);
});
target.items.forEach((dataItem, index) => {
const srcComponent = src.items[index];
if (srcComponent.accessor)
dataItem.accessor = getIndex(src.accessors, srcComponent?.accessor?.componentId);
if (srcComponent.attributes)
dataItem.attributes = getIndex(
src.attributes,
srcComponent?.attributes?.componentId
);
if (srcComponent.typeHint)
dataItem.typeHint = getIndex(src.typeHints, srcComponent?.typeHint?.componentId);
});
target.nodes.forEach((node, index) => {
const srcComponent = src.nodes[index];
if (srcComponent.attributes)
node.attributes = getIndex(src.attributes, srcComponent?.attributes?.componentId);
node.items = srcComponent.items.map((item) => getIndex(src.items, item?.componentId));
node.nodes = srcComponent.nodes.map((node) => getIndex(src.nodes, node?.componentId));
if (srcComponent.typeHint)
node.typeHint = getIndex(src.typeHints, srcComponent?.typeHint?.componentId);
});
}
}