UNPKG

@gltf-transform/extensions

Version:

Adds extension support to @gltf-transform/core

218 lines (194 loc) 7.32 kB
import { type Animation, Extension, type GLTF, type Material, type Mesh, type Node, PropertyType, type ReaderContext, type Scene, type Texture, type WriterContext, } from '@gltf-transform/core'; import { KHR_XMP_JSON_LD } from '../constants.js'; import { Packet } from './packet.js'; type XMPPacketDef = Record<string, unknown>; type XMPParentDef = | GLTF.IAsset | GLTF.IScene | GLTF.INode | GLTF.IMesh | GLTF.IMaterial | GLTF.ITexture | GLTF.IAnimation; interface XMPPropertyDef { packet: number; } interface XMPRootDef { packets?: XMPPacketDef[]; } /** * [KHR_xmp_json_ld](https://github.com/KhronosGroup/gltf/blob/main/extensions/2.0/Khronos/KHR_xmp_json_ld/) * defines XMP metadata associated with a glTF asset. * * XMP metadata provides standardized fields describing the content, provenance, usage * restrictions, or other attributes of a 3D model. XMP metadata does not generally affect the * parsing or runtime behavior of the content — for that, use custom extensions, custom vertex * attributes, or extras. Similarly, storage mechanisms other than XMP should be preferred * for binary content like mesh data, animations, or textures. * * Generally XMP metadata is associated with the entire glTF asset by attaching an XMP {@link Packet} * to the document {@link Root}. In less common cases where metadata must be associated with * specific subsets of a document, XMP Packets may be attached to {@link Scene}, {@link Node}, * {@link Mesh}, {@link Material}, {@link Texture}, or {@link Animation} properties. * * Within each packet, XMP properties become available when an * [XMP namespace](https://www.adobe.io/xmp/docs/XMPNamespaces/) is registered * with {@link Packet.setContext}. Packets cannot use properties whose namespaces are not * registered as context. While not all XMP namespaces are relevant to 3D assets, some common * namespaces provide useful metadata about authorship and provenance. Additionally, the `model3d` * namespace provides certain properties specific to 3D content, such as Augmented Reality (AR) * orientation data. * * Common XMP contexts for 3D models include: * * | Prefix | URI | Name | * |:------------|:--------------------------------------------|:-------------------------------| * | `dc` | http://purl.org/dc/elements/1.1/ | Dublin Core | * | `model3d` | https://schema.khronos.org/model3d/xsd/1.0/ | Model 3D | * | `rdf` | http://www.w3.org/1999/02/22-rdf-syntax-ns# | Resource Description Framework | * | `xmp` | http://ns.adobe.com/xap/1.0/ | XMP | * | `xmpRights` | http://ns.adobe.com/xap/1.0/rights/ | XMP Rights Management | * * Only the XMP contexts required for a packet should be assigned, and different packets * in the same asset may use different contexts. For greater detail on available XMP * contexts and how to use them in glTF assets, see the * [3DC Metadata Recommendations](https://github.com/KhronosGroup/3DC-Metadata-Recommendations/blob/main/model3d.md). * * Properties: * - {@link Packet} * * ### Example * * ```typescript * import { KHRXMP, Packet } from '@gltf-transform/extensions'; * * // Create an Extension attached to the Document. * const xmpExtension = document.createExtension(KHRXMP); * * // Create Packet property. * const packet = xmpExtension.createPacket() * .setContext({ * dc: 'http://purl.org/dc/elements/1.1/', * }) * .setProperty('dc:Creator', {"@list": ["Acme, Inc."]}); * * // Option 1: Assign to Document Root. * document.getRoot().setExtension('KHR_xmp_json_ld', packet); * * // Option 2: Assign to a specific Property. * texture.setExtension('KHR_xmp_json_ld', packet); * ``` */ export class KHRXMP extends Extension { public readonly extensionName: typeof KHR_XMP_JSON_LD = KHR_XMP_JSON_LD; public static readonly EXTENSION_NAME: typeof KHR_XMP_JSON_LD = KHR_XMP_JSON_LD; /** Creates a new XMP packet, to be linked with a {@link Document} or {@link Property Properties}. */ public createPacket(): Packet { return new Packet(this.document.getGraph()); } /** Lists XMP packets currently defined in a {@link Document}. */ public listPackets(): Packet[] { return Array.from(this.properties) as Packet[]; } /** @hidden */ public read(context: ReaderContext): this { const extensionDef = context.jsonDoc.json.extensions?.[KHR_XMP_JSON_LD] as XMPRootDef | undefined; if (!extensionDef || !extensionDef.packets) return this; // Deserialize packets. const json = context.jsonDoc.json; const root = this.document.getRoot(); const packets = extensionDef.packets.map((packetDef) => this.createPacket().fromJSONLD(packetDef)); const defLists = [ [json.asset], json.scenes, json.nodes, json.meshes, json.materials, json.images, json.animations, ]; const propertyLists = [ [root], root.listScenes(), root.listNodes(), root.listMeshes(), root.listMaterials(), root.listTextures(), root.listAnimations(), ]; // Assign packets. for (let i = 0; i < defLists.length; i++) { const defs = defLists[i] || []; for (let j = 0; j < defs.length; j++) { const def = defs[j]; if (def.extensions && def.extensions[KHR_XMP_JSON_LD]) { const xmpDef = def.extensions[KHR_XMP_JSON_LD] as XMPPropertyDef; propertyLists[i][j].setExtension(KHR_XMP_JSON_LD, packets[xmpDef.packet]); } } } return this; } /** @hidden */ public write(context: WriterContext): this { const { json } = context.jsonDoc; const packetDefs = []; for (const packet of this.properties as Set<Packet>) { // Serialize packets. packetDefs.push(packet.toJSONLD()); // Assign packets. for (const parent of packet.listParents()) { let parentDef: XMPParentDef | null; switch (parent.propertyType) { case PropertyType.ROOT: parentDef = json.asset; break; case PropertyType.SCENE: parentDef = json.scenes![context.sceneIndexMap.get(parent as Scene)!]; break; case PropertyType.NODE: parentDef = json.nodes![context.nodeIndexMap.get(parent as Node)!]; break; case PropertyType.MESH: parentDef = json.meshes![context.meshIndexMap.get(parent as Mesh)!]; break; case PropertyType.MATERIAL: parentDef = json.materials![context.materialIndexMap.get(parent as Material)!]; break; case PropertyType.TEXTURE: parentDef = json.images![context.imageIndexMap.get(parent as Texture)!]; break; case PropertyType.ANIMATION: parentDef = json.animations![context.animationIndexMap.get(parent as Animation)!]; break; default: parentDef = null; this.document .getLogger() .warn(`[${KHR_XMP_JSON_LD}]: Unsupported parent property, "${parent.propertyType}"`); break; } if (!parentDef) continue; parentDef.extensions = parentDef.extensions || {}; parentDef.extensions[KHR_XMP_JSON_LD] = { packet: packetDefs.length - 1 }; } } if (packetDefs.length > 0) { json.extensions = json.extensions || {}; json.extensions[KHR_XMP_JSON_LD] = { packets: packetDefs }; } return this; } }