import * as path from 'path' ; import * as fse from 'fs-extra' ; import Zip from 'adm-zip' ; import { isNullOrUndefined } from 'util' ; import { ModelDerivativeClient , ManifestHelper , IDerivativeResourceChild } from 'forge-server-utils' ; import { IAuthOptions , Region } from 'forge-server-utils/dist/common' ; import { PropDbReader } from '../common/propdb-reader' ; import { parseFragments } from './fragments' ; import { parseGeometries } from './geometries' ; import { parseMaterials } from './materials' ; import { parseMeshes } from './meshes' ; import * as SVF from './schema' ; import * as IMF from '../common/intermediate-format' ; /**
 * Entire content of SVF and its assets loaded in memory.
 */ export interface ISvfContent { metadata : SVF . ISvfMetadata ; fragments : SVF . IFragment [ ] ; geometries : SVF . IGeometryMetadata [ ] ; meshpacks : ( SVF . IMesh | SVF . ILines | SVF . IPoints | null ) [ ] [ ] ; materials : ( SVF . IMaterial | null ) [ ] ; properties : PropDbReader ; images : { [ uri ] : Buffer ; } ; } export class Scene implements IMF . IScene { constructor ( protected svf ) { } getMetadata ( ) { return this . svf . metadata . metadata ; } getNodeCount ( ) { return this . svf . fragments . length ; } getNode ( id ) { const frag = this . svf . fragments [ id ] ; const node = { kind : IMF . NodeKind . Object , dbid : frag . dbID , geometry : frag . geometryID , material : frag . materialID } ; if ( frag . transform ) { if ( 'matrix' in frag . transform ) { const { matrix , t } = frag . transform ; node . transform = { kind : IMF . TransformKind . Matrix , elements : [ matrix [ 0 ] , matrix [ 1 ] , matrix [ 2 ] , 0 , matrix [ 3 ] , matrix [ 4 ] , matrix [ 5 ] , 0 , matrix [ 6 ] , matrix [ 7 ] , matrix [ 8 ] , 0 , t ? t . x : 0 , t ? t . y : 0 , t ? t . z : 0 , 1 ] } ; } else { node . transform = { kind : IMF . TransformKind . Decomposed } ; if ( 'q' in frag . transform ) { node . transform . rotation = frag . transform . q ; } if ( 's' in frag . transform ) { node . transform . scale = frag . transform . s ; } if ( 't' in frag . transform ) { node . transform . translation = frag . transform . t ; } } } return node ; } getGeometryCount ( ) { return this . svf . geometries . length ; } getGeometry ( id ) { const meta = this . svf . geometries [ id ] ; const mesh = this . svf . meshpacks [ meta . packID ] [ meta . entityID ] ; if ( mesh ) { if ( 'isLines' in mesh ) { const geom = { kind : IMF . GeometryKind . Lines , getIndices : ( ) => mesh . indices , getVertices : ( ) => mesh . vertices , getColors : ( ) => mesh . colors } ; return geom ; } else if ( 'isPoints' in mesh ) { const geom = { kind : IMF . GeometryKind . Points , getVertices : ( ) => mesh . vertices , getColors : ( ) => mesh . colors } ; return geom ; } else { const geom = { kind : IMF . GeometryKind . Mesh , getIndices : ( ) => mesh . indices , getVertices : ( ) => mesh . vertices , getNormals : ( ) => mesh . normals , getColors : ( ) => mesh . colors , getUvChannelCount : ( ) => mesh . uvcount , getUvs : ( channel ) => mesh . uvmaps [ channel ] . uvs } ; return geom ; } } return { kind : IMF . GeometryKind . Empty } ; } getMaterialCount ( ) { return this . svf . materials . length ; } getMaterial ( id ) { const _mat = this . svf . materials [ id ] ; const mat = { kind : IMF . MaterialKind . Physical , diffuse : { x : 0 , y : 0 , z : 0 } , metallic : _mat ?. metal ? 1.0 : 0.0 , opacity : _mat ?. opacity ?? 1.0 , roughness : _mat ?. glossiness ? ( 1.0 - _mat . glossiness / 255.0 ) : 1.0 , scale : { x : _mat ?. maps ?. diffuse ?. scale . texture_UScale ?? 1.0 , y : _mat ?. maps ?. diffuse ?. scale . texture_VScale ?? 1.0 } } ; if ( _mat ?. diffuse ) { mat . diffuse . x = _mat . diffuse [ 0 ] ; mat . diffuse . y = _mat . diffuse [ 1 ] ; mat . diffuse . z = _mat . diffuse [ 2 ] ; } if ( _mat ?. maps ?. diffuse ) { mat . maps = mat . maps || { } ; mat . maps . diffuse = _mat . maps . diffuse . uri ; } return mat ; } getImage ( uri ) { return this . svf . images [ uri ] ; } } /**
 * Additional options when reading the entire SVF.
 */ export interface IReaderOptions { log ? : ( msg : string ) => void ; skipPropertyDb ? : boolean ; } /**
 * Utility class for parsing & reading SVF content from Model Derivative service
 * or from local file system.
 *
 * The class can only be instantiated using one of the two async static methods:
 *  Reader . FromFileSystem , or  Reader . FromDerivativeService .
 * After that, you can parse the entire SVF into memory using  parse , or parse
 * individual SVF objects using methods like  readFragments or  enumerateGeometries .
 *
 *  example example export class Reader { async param { string } filepath returns { Promise < Reader > } static async FromFileSystem ( filepath ) { const svf = fse . readFileSync ( filepath ) ; const baseDir = path . dirname ( filepath ) ; const resolve = async ( uri ) => { const buffer = fse . readFileSync ( path . join ( baseDir , uri ) ) ; return buffer ; } ; return new Reader ( svf , resolve ) ; } async param { string } urn param { string } guid param { IAuthOptions } auth param { string } host param { Region } region returns { Promise < Reader > } static async FromDerivativeService ( urn , guid , auth , host ? , region ? ) { urn = urn . replace ( /=/g , '' ) ; const modelDerivativeClient = new ModelDerivativeClient ( auth , host , region ) ; const helper = new ManifestHelper ( await modelDerivativeClient . getManifest ( urn ) ) ; const resources = helper . search ( { type : 'resource' , role : 'graphics' , guid } ) ; if ( resources . length === 0 ) { throw new Error ( `Viewable '${ guid }' not found.` ) ; } const svfUrn = ( resources [ 0 ] as IDerivativeResourceChild ) . urn ; const svf = await modelDerivativeClient . getDerivative ( urn , encodeURI ( svfUrn ) ) as Buffer ; const baseUri = svfUrn . substr ( 0 , svfUrn . lastIndexOf ( '/' ) ) ; const resolve = async ( uri ) => { const encodedUri = encodeURI ( baseUri + '/' + uri ) ; const buffer = await modelDerivativeClient . getDerivative ( urn , encodedUri ) as Buffer ; return buffer ; } ; return new Reader ( svf , resolve ) ; } protected svf : SVF . ISvfRoot ; protected constructor ( svf , protected resolve ) { const zip = new Zip ( svf ) ; const manifestEntry = zip . getEntry ( 'manifest.json' ) ; const metadataEntry = zip . getEntry ( 'metadata.json' ) ; if ( ! manifestEntry ) { throw new Error ( 'Missing SVF asset: manifest.js' ) ; } if ( ! metadataEntry ) { throw new Error ( 'Missing SVF asset: metadata.js' ) ; } const manifest = JSON . parse ( manifestEntry . getData ( ) . toString ( ) ) as SVF . ISvfManifest ; const metadata = JSON . parse ( metadataEntry . getData ( ) . toString ( ) ) as SVF . ISvfMetadata ; const embedded = { } ; zip . getEntries ( ) . filter ( entry => entry . name !== 'manifest.json' && entry . name !== 'metadata.json' ) . forEach ( ( entry ) => { embedded [ entry . name ] = entry . getData ( ) ; } ) ; this . svf = { manifest , metadata , embedded } ; } /**
     * Reads the entire scene and all its referenced assets into memory.
     * In cases where a more granular control is needed (for example, when trying to control
     * memory consumption), consider parsing the different SVF elements individually,
     * using methods like  readFragments ,  enumerateGeometries , etc.
     *  async param { IReaderOptions } options returns { Promise < IMF . IScene > } async read ( options ? ) { let output = { metadata : await this . getMetadata ( ) , fragments : [ ] , geometries : [ ] , meshpacks : [ ] , materials : [ ] , properties : null , images : { } } ; let tasks = [ ] ; const log = ( options && options . log ) || function ( msg ) { } ; tasks . push ( ( async ( ) => { log ( `Reading fragments...` ) ; output . fragments = await this . readFragments ( ) ; log ( `Reading fragments: done` ) ; } ) ( ) ) ; tasks . push ( ( async ( ) => { log ( `Reading geometries...` ) ; output . geometries = await this . readGeometries ( ) ; log ( `Reading geometries: done` ) ; } ) ( ) ) ; tasks . push ( ( async ( ) => { log ( `Reading materials...` ) ; output . materials = await this . readMaterials ( ) ; log ( `Reading materials: done` ) ; } ) ( ) ) ; if ( ! ( options && options . skipPropertyDb ) ) { tasks . push ( ( async ( ) => { log ( `Reading property database...` ) ; output . properties = await this . getPropertyDb ( ) ; log ( `Reading property database: done` ) ; } ) ( ) ) ; } for ( let i = 0 , len = this . getMeshPackCount ( ) ; i < len ; i ++ ) { tasks . push ( ( async ( id ) => { log ( `Reading meshpack #${ id }...` ) ; output . meshpacks [ id ] = await this . readMeshPack ( id ) ; log ( `Reading meshpack #${ id }: done` ) ; } ) ( i ) ) ; } for ( const img of this . listImages ( ) ) { tasks . push ( ( async ( uri ) => { log ( `Downloading image ${ uri }...` ) ; const normalizedUri = uri . toLowerCase ( ) . split ( /[\/\\]/ ) . join ( path . sep ) ; let imageData = null ; try { imageData = await this . getAsset ( uri ) ; } catch ( err ) { } if ( ! imageData ) { log ( `Could not find image ${ uri }, trying a lower-cased version of the URI...` ) ; try { imageData = await this . getAsset ( uri . toLowerCase ( ) ) ; } catch ( err ) { } } if ( ! imageData ) { log ( `Still could not find image ${ uri }; will default to a single black pixel placeholder image...` ) ; imageData = undefined ; } output . images [ normalizedUri ] = imageData ; log ( `Downloading image ${ uri }: done` ) ; } ) ( img ) ) ; } await Promise . all ( tasks ) ; return new Scene ( output ) ; } protected findAsset ( query ) { return this . svf . manifest . assets . find ( asset => { return ( isNullOrUndefined ( query . type ) || asset . type === query . type ) && ( isNullOrUndefined ( query . uri ) || asset . URI === query . uri ) ; } ) ; } async param { string } uri returns { Promise < Buffer > } async getAsset ( uri ) { return this . resolve ( uri ) ; } async returns { Promise < SVF . ISvfMetadata > } async getMetadata ( ) { return this . svf . metadata ; } async returns { Promise < SVF . ISvfManifest > } async getManifest ( ) { return this . svf . manifest ; } async generator returns { AsyncIterable < SVF . IFragment > } async * enumerateFragments ( ) { const fragmentAsset = this . findAsset ( { type : SVF . AssetType . FragmentList } ) ; if ( ! fragmentAsset ) { throw new Error ( `Fragment list not found.` ) ; } const buffer = await this . getAsset ( fragmentAsset . URI ) ; for ( const fragment of parseFragments ( buffer ) ) { yield fragment ; } } async returns { Promise < IFragment [ ] > } async readFragments ( ) { const fragmentAsset = this . findAsset ( { type : SVF . AssetType . FragmentList } ) ; if ( ! fragmentAsset ) { throw new Error ( `Fragment list not found.` ) ; } const buffer = await this . getAsset ( fragmentAsset . URI ) ; return Array . from ( parseFragments ( buffer ) ) ; } async generator returns { AsyncIterable < SVF . IGeometryMetadata > } async * enumerateGeometries ( ) { const geometryAsset = this . findAsset ( { type : SVF . AssetType . GeometryMetadataList } ) ; if ( ! geometryAsset ) { throw new Error ( `Geometry metadata not found.` ) ; } const buffer = await this . getAsset ( geometryAsset . URI ) ; for ( const geometry of parseGeometries ( buffer ) ) { yield geometry ; } } async returns { Promise < SVF . IGeometryMetadata [ ] > } async readGeometries ( ) { const geometryAsset = this . findAsset ( { type : SVF . AssetType . GeometryMetadataList } ) ; if ( ! geometryAsset ) { throw new Error ( `Geometry metadata not found.` ) ; } const buffer = await this . getAsset ( geometryAsset . URI ) ; return Array . from ( parseGeometries ( buffer ) ) ; } /**
     * Gets the number of available mesh packs.
     */ getMeshPackCount ( ) { let count = 0 ; this . svf . manifest . assets . forEach ( asset => { if ( asset . type === SVF . AssetType . PackFile && asset . URI . match ( /^\d+\.pf$/ ) ) { count ++ ; } } ) ; return count ; } async generator returns { AsyncIterable < SVF . IMesh | SVF . ILines | SVF . IPoints | null > } async * enumerateMeshPack ( packNumber ) { const meshPackAsset = this . findAsset ( { type : SVF . AssetType . PackFile , uri : `${ packNumber }.pf` } ) ; if ( ! meshPackAsset ) { throw new Error ( `Mesh packfile ${ packNumber }.pf not found.` ) ; } const buffer = await this . getAsset ( meshPackAsset . URI ) ; for ( const mesh of parseMeshes ( buffer ) ) { yield mesh ; } } async param { number } packNumber returns { Promise < ( SVF . IMesh | SVF . ILines | SVF . IPoints | null ) [ ] > } async readMeshPack ( packNumber ) { const meshPackAsset = this . findAsset ( { type : SVF . AssetType . PackFile , uri : `${ packNumber }.pf` } ) ; if ( ! meshPackAsset ) { throw new Error ( `Mesh packfile ${ packNumber }.pf not found.` ) ; } const buffer = await this . getAsset ( meshPackAsset . URI ) ; return Array . from ( parseMeshes ( buffer ) ) ; } async generator returns { AsyncIterable < SVF . IMaterial | null > } async * enumerateMaterials ( ) { const materialsAsset = this . findAsset ( { type : SVF . AssetType . ProteinMaterials , uri : `Materials.json.gz` } ) ; if ( ! materialsAsset ) { throw new Error ( `Materials not found.` ) ; } const buffer = await this . getAsset ( materialsAsset . URI ) ; for ( const material of parseMaterials ( buffer ) ) { yield material ; } } async returns { Promise < ( SVF . IMaterial | null ) [ ] > } async readMaterials ( ) { const materialsAsset = this . findAsset ( { type : SVF . AssetType . ProteinMaterials , uri : `Materials.json.gz` } ) ; if ( ! materialsAsset ) { throw new Error ( `Materials not found.` ) ; } const buffer = await this . getAsset ( materialsAsset . URI ) ; return Array . from ( parseMaterials ( buffer ) ) ; } /**
     * Finds URIs of all image assets referenced in the SVF.
     * These can then be retrieved using  getAsset .
     *  returns { string [ ] } listImages ( ) { return this . svf . manifest . assets . filter ( asset => asset . type === SVF . AssetType . Image ) . map ( asset => asset . URI ) ; } async returns { Promise < PropDbReader > } async getPropertyDb ( ) { const idsAsset = this . findAsset ( { type : SVF . AssetType . PropertyIDs } ) ; const offsetsAsset = this . findAsset ( { type : SVF . AssetType . PropertyOffsets } ) ; const avsAsset = this . findAsset ( { type : SVF . AssetType . PropertyAVs } ) ; const attrsAsset = this . findAsset ( { type : SVF . AssetType . PropertyAttributes } ) ; const valsAsset = this . findAsset ( { type : SVF . AssetType . PropertyValues } ) ; if ( ! idsAsset || ! offsetsAsset || ! avsAsset || ! attrsAsset || ! valsAsset ) { throw new Error ( 'Could not parse property database. Some of the database assets are missing.' ) ; } const buffers = await Promise . all ( [ this . getAsset ( idsAsset . URI ) , this . getAsset ( offsetsAsset . URI ) , this . getAsset ( avsAsset . URI ) , this . getAsset ( attrsAsset . URI ) , this . getAsset ( valsAsset . URI ) ] ) ; return new PropDbReader ( buffers [ 0 ] , buffers [ 1 ] , buffers [ 2 ] , buffers [ 3 ] , buffers [ 4 ] ) ; } }