From 73a766d7c48266b4d1e8e6a6a5fdfd5899cc11c2 Mon Sep 17 00:00:00 2001 From: Jason Gardner Date: Sat, 18 Jan 2025 11:01:29 -0600 Subject: [PATCH 1/5] feat: USDZ Export --- plugins/usd_codec/icon.png | Bin 0 -> 13039 bytes plugins/usd_codec/icon.svg | 1 + plugins/usd_codec/usd_codec.js | 422 ++++++++++++++++ src/pbr_preview/src/lib/io/UsdzExporter.ts | 7 +- src/usd_codec/src/index.ts | 28 ++ .../src/io/formats/usdz/USDZExporter.ts | 475 ++++++++++++++++++ src/usd_codec/src/io/formats/usdz/index.ts | 78 +++ src/usd_codec/tsconfig.json | 26 + 8 files changed, 1033 insertions(+), 4 deletions(-) create mode 100644 plugins/usd_codec/icon.png create mode 100644 plugins/usd_codec/icon.svg create mode 100644 plugins/usd_codec/usd_codec.js create mode 100644 src/usd_codec/src/index.ts create mode 100644 src/usd_codec/src/io/formats/usdz/USDZExporter.ts create mode 100644 src/usd_codec/src/io/formats/usdz/index.ts create mode 100644 src/usd_codec/tsconfig.json diff --git a/plugins/usd_codec/icon.png b/plugins/usd_codec/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7c303d0f4ae41a9778befc2941abfb930e1b6bca GIT binary patch literal 13039 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 literal 0 HcmV?d00001 diff --git a/plugins/usd_codec/icon.svg b/plugins/usd_codec/icon.svg new file mode 100644 index 00000000..b7f1f87c --- /dev/null +++ b/plugins/usd_codec/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/usd_codec/usd_codec.js b/plugins/usd_codec/usd_codec.js new file mode 100644 index 00000000..95f61e17 --- /dev/null +++ b/plugins/usd_codec/usd_codec.js @@ -0,0 +1,422 @@ +// src/io/formats/usdz/USDZExporter.ts +class USDZExporter { + async parse(scene) { + const zip = new JSZip; + const modelFileName = "model.usda"; + zip.file(modelFileName, ""); + let output = buildHeader(); + const materials = {}; + const textures = {}; + scene.traverseVisible((object) => { + if (!object.isMesh) { + return; + } + const mesh = object; + if (!mesh.material.isMeshStandardMaterial) { + console.warn("THREE.USDZExporter: Unsupported material type (USDZ only supports MeshStandardMaterial)", object); + return; + } + const geometry = mesh.geometry; + const material = mesh.material; + const geometryFileName = "geometries/Geometry_" + geometry.id + ".usd"; + if (!zip.file(geometryFileName)) { + const meshObject = buildMeshObject(geometry); + zip.file(geometryFileName, buildUSDFileAsString(meshObject)); + } + if (!(material.uuid in materials)) { + materials[material.uuid] = material; + } + output += buildXform(mesh, geometry, material); + }); + output += buildMaterials(materials, textures); + zip.file(modelFileName, output); + output = null; + for (const id in textures) { + const texture = textures[id]; + const color = id.split("_")[1]; + const isRGBA = texture.format === THREE.RGBAFormat; + const canvas = imageToCanvas(texture.image, color); + const blob = await new Promise((resolve) => canvas.toBlob((v) => v && resolve(v), isRGBA ? "image/png" : "image/jpeg", 1)); + const arrayBuffer = await blob.arrayBuffer(); + zip.file(`textures/Texture_${id}.${isRGBA ? "png" : "jpg"}`, arrayBuffer); + } + let offset = 0; + zip.forEach(async (relativePath) => { + const headerSize = 34 + relativePath.length; + offset += headerSize; + const offsetMod64 = offset & 63; + const content = await zip.file(relativePath)?.async("uint8array"); + if (!content) { + return; + } + if (offsetMod64 !== 4) { + const padLength = 64 - offsetMod64; + const padding = new Uint8Array(padLength); + const newContent = new Uint8Array(content.length + padLength); + newContent.set(content, 0); + newContent.set(padding, content.length); + zip.file(relativePath, newContent); + } + offset += content.length; + }); + const zipBlob = await zip.generateAsync({ + type: "blob", + compression: "STORE" + }); + return new Uint8Array(await zipBlob.arrayBuffer()); + } +} +function imageToCanvas(image, color) { + if (typeof HTMLImageElement !== "undefined" && image instanceof HTMLImageElement || typeof HTMLCanvasElement !== "undefined" && image instanceof HTMLCanvasElement || typeof OffscreenCanvas !== "undefined" && image instanceof OffscreenCanvas || typeof ImageBitmap !== "undefined" && image instanceof ImageBitmap) { + const scale = 1024 / Math.max(image.width, image.height); + const canvas = document.createElement("canvas"); + canvas.width = image.width * Math.min(1, scale); + canvas.height = image.height * Math.min(1, scale); + const context = canvas.getContext("2d"); + context.imageSmoothingEnabled = false; + context.drawImage(image, 0, 0, canvas.width, canvas.height); + if (color !== undefined) { + const hex = parseInt(color, 16); + const r = (hex >> 16 & 255) / 255; + const g = (hex >> 8 & 255) / 255; + const b = (hex & 255) / 255; + const imagedata = context.getImageData(0, 0, canvas.width, canvas.height); + const data = imagedata.data; + for (let i = 0;i < data.length; i += 4) { + data[i + 0] = data[i + 0] * r; + data[i + 1] = data[i + 1] * g; + data[i + 2] = data[i + 2] * b; + } + context.putImageData(imagedata, 0, 0); + } + return canvas; + } + throw new Error("Unsupported image type"); +} +var PRECISION = 7; +function buildHeader() { + return `#usda 1.0 +( + customLayerData = { + string creator = "Blockbench USDZExporter" + } + metersPerUnit = 1 + upAxis = "Y" +) + +`; +} +function buildUSDFileAsString(dataToInsert) { + let output = buildHeader(); + output += dataToInsert; + return output; +} +function buildXform(object, geometry, material) { + const name = "Object_" + object.id; + const transform = buildMatrix(object.matrixWorld); + if (object.matrixWorld.determinant() < 0) { + console.warn("THREE.USDZExporter: USDZ does not support negative scales", object); + } + return `def Xform "${name}" ( + prepend references = @./geometries/Geometry_${geometry.id}.usd@ +) +{ + matrix4d xformOp:transform = ${transform} + uniform token[] xformOpOrder = ["xformOp:transform"] + + rel material:binding = +} + +`; +} +function buildMatrix(matrix) { + const array = matrix.elements; + return `( ${buildMatrixRow(array, 0)}, ${buildMatrixRow(array, 4)}, ${buildMatrixRow(array, 8)}, ${buildMatrixRow(array, 12)} )`; +} +function buildMatrixRow(array, offset) { + return `(${array[offset + 0]}, ${array[offset + 1]}, ${array[offset + 2]}, ${array[offset + 3]})`; +} +function buildMeshObject(geometry) { + const mesh = buildMesh(geometry); + return ` +def "Geometry" +{ + ${mesh} +} +`; +} +function buildMesh(geometry) { + const name = "Geometry"; + const attributes = geometry.attributes; + const count = attributes.position.count; + return ` + def Mesh "${name}" + { + int[] faceVertexCounts = [${buildMeshVertexCount(geometry)}] + int[] faceVertexIndices = [${buildMeshVertexIndices(geometry)}] + normal3f[] normals = [${buildVector3Array(attributes.normal, count)}] ( + interpolation = "vertex" + ) + point3f[] points = [${buildVector3Array(attributes.position, count)}] + float2[] primvars:st = [${buildVector2Array(attributes.uv, count)}] ( + interpolation = "vertex" + ) + uniform token subdivisionScheme = "none" + } +`; +} +function buildMeshVertexCount(geometry) { + const count = geometry.index !== null ? geometry.index.count : geometry.attributes.position.count; + return Array(count / 3).fill(3).join(", "); +} +function buildMeshVertexIndices(geometry) { + const index = geometry.index; + const array = []; + if (index !== null) { + for (let i = 0;i < index.count; i++) { + array.push(index.getX(i)); + } + } else { + const length = geometry.attributes.position.count; + for (let i = 0;i < length; i++) { + array.push(i); + } + } + return array.join(", "); +} +function buildVector3Array(attribute, count) { + if (attribute === undefined) { + console.warn("USDZExporter: Normals missing."); + return Array(count).fill("(0, 0, 0)").join(", "); + } + const array = []; + for (let i = 0;i < attribute.count; i++) { + const x = attribute.getX(i); + const y = attribute.getY(i); + const z = attribute.getZ(i); + array.push(`(${x.toPrecision(PRECISION)}, ${y.toPrecision(PRECISION)}, ${z.toPrecision(PRECISION)})`); + } + return array.join(", "); +} +function buildVector2Array(attribute, count) { + if (attribute === undefined) { + console.warn("USDZExporter: UVs missing."); + return Array(count).fill("(0, 0)").join(", "); + } + const array = []; + for (let i = 0;i < attribute.count; i++) { + const x = attribute.getX(i); + const y = attribute.getY(i); + array.push(`(${x.toPrecision(PRECISION)}, ${Number(y.toPrecision(PRECISION))})`); + } + return array.join(", "); +} +function buildMaterials(materials, textures) { + const array = []; + for (const uuid in materials) { + const material = materials[uuid]; + array.push(buildMaterial(material, textures)); + } + return `def "Materials" +{ +${array.join("")} +} + +`; +} +function buildMaterial(material, textures) { + const pad = " "; + const inputs = []; + const samplers = []; + function buildTexture(texture, mapType, color) { + const id = texture.id + (color ? "_" + color.getHexString() : ""); + const isRGBA = texture.format === THREE.RGBAFormat; + textures[id] = texture; + return ` + def Shader "Transform2d_${mapType}" ( + sdrMetadata = { + string role = "math" + } + ) + { + uniform token info:id = "UsdTransform2d" + float2 inputs:in.connect = + float2 inputs:scale = ${buildVector2(texture.repeat)} + float2 inputs:translation = ${buildVector2(texture.offset)} + float2 outputs:result + } + + def Shader "Texture_${texture.id}_${mapType}" + { + uniform token info:id = "UsdUVTexture" + asset inputs:file = @textures/Texture_${id}.${isRGBA ? "png" : "jpg"}@ + float2 inputs:st.connect = + token inputs:wrapS = "repeat" + token inputs:wrapT = "repeat" + float outputs:r + float outputs:g + float outputs:b + float3 outputs:rgb + }`; + } + const mat = material; + if (mat.map !== null) { + inputs.push(`${pad}color3f inputs:diffuseColor.connect = `); + samplers.push(buildTexture(mat.map, "diffuse", mat.color)); + } else { + inputs.push(`${pad}color3f inputs:diffuseColor = ${buildColor(mat.color)}`); + } + if (mat.emissiveMap !== null) { + inputs.push(`${pad}color3f inputs:emissiveColor.connect = `); + samplers.push(buildTexture(mat.emissiveMap, "emissive")); + } else if (mat.emissive.getHex() > 0) { + inputs.push(`${pad}color3f inputs:emissiveColor = ${buildColor(mat.emissive)}`); + } + if (mat.normalMap !== null) { + inputs.push(`${pad}normal3f inputs:normal.connect = `); + samplers.push(buildTexture(mat.normalMap, "normal")); + } + if (mat.aoMap !== null) { + inputs.push(`${pad}float inputs:occlusion.connect = `); + samplers.push(buildTexture(mat.aoMap, "occlusion")); + } + if (mat.roughnessMap !== null && mat.roughness === 1) { + inputs.push(`${pad}float inputs:roughness.connect = `); + samplers.push(buildTexture(mat.roughnessMap, "roughness")); + } else { + inputs.push(`${pad}float inputs:roughness = ${mat.roughness}`); + } + if (mat.metalnessMap !== null && mat.metalness === 1) { + inputs.push(`${pad}float inputs:metallic.connect = `); + samplers.push(buildTexture(mat.metalnessMap, "metallic")); + } else { + inputs.push(`${pad}float inputs:metallic = ${mat.metalness}`); + } + if (mat.alphaMap !== null) { + inputs.push(`${pad}float inputs:opacity.connect = `); + inputs.push(`${pad}float inputs:opacityThreshold = 0.0001`); + samplers.push(buildTexture(mat.alphaMap, "opacity")); + } else { + inputs.push(`${pad}float inputs:opacity = ${mat.opacity}`); + } + if (mat.isMeshPhysicalMaterial) { + const physicalMat = mat; + inputs.push(`${pad}float inputs:clearcoat = ${physicalMat.clearcoat}`); + inputs.push(`${pad}float inputs:clearcoatRoughness = ${physicalMat.clearcoatRoughness}`); + inputs.push(`${pad}float inputs:ior = ${physicalMat.ior}`); + } + return ` + def Material "Material_${mat.id}" + { + def Shader "PreviewSurface" + { + uniform token info:id = "UsdPreviewSurface" +${inputs.join(` +`)} + int inputs:useSpecularWorkflow = 0 + token outputs:surface + } + + token outputs:surface.connect = + token inputs:frame:stPrimvarName = "st" + + def Shader "uvReader_st" + { + uniform token info:id = "UsdPrimvarReader_float2" + token inputs:varname.connect = + float2 inputs:fallback = (0.0, 0.0) + float2 outputs:result + } + +${samplers.join(` +`)} + + } +`; +} +function buildColor(color) { + return `(${color.r}, ${color.g}, ${color.b})`; +} +function buildVector2(vector) { + return `(${vector.x}, ${vector.y})`; +} +var USDZExporter_default = USDZExporter; + +// src/io/formats/usdz/index.ts +function getOutputBaseName() { + if (!Project) { + return pathToName(Texture.selected?.name ?? "texture"); + } + return Project.model_identifier.length > 0 ? Project.model_identifier : Project.getDisplayName(); +} +function setup() { + const usdz = new Codec("usdz", { + extension: "usdz", + name: "USDZ", + remember: true, + fileName() { + return getOutputBaseName() + ".usdz"; + }, + async compile(compileOptions = {}) { + if (!Project) { + throw new Error("No project loaded"); + } + const options = Object.assign(this.export_options ?? {}, compileOptions); + const exporter = new USDZExporter_default; + const scene = new window.THREE.Scene; + scene.name = "blockbench_export"; + scene.add(Project.model_3d); + const result = await exporter.parse(scene); + this.dispatchEvent("compile", { model: result, options }); + Canvas.scene.add(Project.model_3d); + return result; + }, + async export(options = {}) { + const content = await this.compile(options); + Blockbench.export({ + content, + name: this.fileName(), + startpath: this.startPath(), + resource_id: "usdz", + type: this.name, + extensions: ["usdz"], + savetype: "buffer" + }, (path) => this.afterDownload(path)); + } + }); + const exportUsdz = new Action("export_usdz", { + category: "file", + name: "Export USDZ", + description: "Exports the current model as a USDZ file", + icon: "stacks", + async click() { + if (!usdz) { + return; + } + usdz.export(); + } + }); + MenuBar.addAction(exportUsdz, "file.export"); +} +function teardown() { + MenuBar.removeAction("file.export.export_usdz"); +} + +// src/index.ts +(() => { + BBPlugin.register("usd_codec", { + version: "1.0.0", + title: "USD Codec", + author: "Jason J. Gardner", + description: "Export Universal Scene Descriptor (USD) files for use in 3D applications like Blender, Maya, and Houdini.", + tags: ["Codec", "PBR"], + icon: "icon.png", + variant: "both", + await_loading: true, + repository: "https://github.com/jasonjgardner/blockbench-plugins", + has_changelog: false, + min_version: "4.12.1", + onload: setup, + onunload: teardown + }); +})(); diff --git a/src/pbr_preview/src/lib/io/UsdzExporter.ts b/src/pbr_preview/src/lib/io/UsdzExporter.ts index d7fd6b68..ed8a97ce 100644 --- a/src/pbr_preview/src/lib/io/UsdzExporter.ts +++ b/src/pbr_preview/src/lib/io/UsdzExporter.ts @@ -1,8 +1,7 @@ -import { three as THREE, jszip as JSZip } from "../../deps"; - class USDZExporter { async parse(scene: THREE.Scene): Promise { - const zip = new JSZip(); + // @ts-expect-error JSZip is on window + const zip = new window.JSZip(); const modelFileName = "model.usda"; // model file should be first in USDZ archive so we init it here zip.file(modelFileName, ""); @@ -45,7 +44,7 @@ class USDZExporter { for (const id in textures) { const texture = textures[id]; const color = id.split("_")[1]; - const isRGBA = texture.format === THREE.RGBAFormat; + const isRGBA = texture.format === window.THREE.RGBAFormat; const canvas = imageToCanvas(texture.image, color); const blob = await new Promise((resolve) => canvas.toBlob( diff --git a/src/usd_codec/src/index.ts b/src/usd_codec/src/index.ts new file mode 100644 index 00000000..facc8553 --- /dev/null +++ b/src/usd_codec/src/index.ts @@ -0,0 +1,28 @@ +/** + * @author jasonjgardner + * @discord jason.gardner + * @github https://github.com/jasonjgardner + */ +/// +/// + +import { setup, teardown } from './io/formats/usdz' + +(() => { + BBPlugin.register("usd_codec", { + version: "1.0.0", + title: "USD Codec", + author: "Jason J. Gardner", + description: + "Export Universal Scene Descriptor (USD) files for use in 3D applications like Blender, Maya, and Houdini.", + tags: ["Codec","PBR"], + icon: "icon.png", + variant: "both", + await_loading: true, + repository: "https://github.com/jasonjgardner/blockbench-plugins", + has_changelog: false, + min_version: "4.12.1", + onload: setup, + onunload: teardown, + }); +})(); diff --git a/src/usd_codec/src/io/formats/usdz/USDZExporter.ts b/src/usd_codec/src/io/formats/usdz/USDZExporter.ts new file mode 100644 index 00000000..c5dd7e18 --- /dev/null +++ b/src/usd_codec/src/io/formats/usdz/USDZExporter.ts @@ -0,0 +1,475 @@ +class USDZExporter { + async parse(scene: THREE.Scene): Promise { + // @ts-expect-error JSZip is on window + const zip = new JSZip(); + const modelFileName = "model.usda"; // model file should be first in USDZ archive so we init it here + + zip.file(modelFileName, ""); + let output: string | null = buildHeader(); + const materials: Record = {}; + const textures: Record = {}; + scene.traverseVisible((object) => { + if (!(object as THREE.Mesh).isMesh) { + return; + } + const mesh = object as THREE.Mesh; + if ( + !(mesh.material as THREE.MeshStandardMaterial).isMeshStandardMaterial + ) { + console.warn( + "THREE.USDZExporter: Unsupported material type (USDZ only supports MeshStandardMaterial)", + object + ); + return; + } + const geometry = mesh.geometry; + const material = mesh.material as THREE.MeshStandardMaterial; + const geometryFileName = "geometries/Geometry_" + geometry.id + ".usd"; + + if (!zip.file(geometryFileName)) { + const meshObject = buildMeshObject(geometry); + zip.file(geometryFileName, buildUSDFileAsString(meshObject)); + } + + if (!(material.uuid in materials)) { + materials[material.uuid] = material; + } + + output += buildXform(mesh, geometry, material); + }); + output += buildMaterials(materials, textures); + zip.file(modelFileName, output); + output = null; + + for (const id in textures) { + const texture = textures[id]; + const color = id.split("_")[1]; + const isRGBA = texture.format === THREE.RGBAFormat; + const canvas = imageToCanvas(texture.image, color); + const blob = await new Promise((resolve) => + canvas.toBlob( + (v) => v && resolve(v), + isRGBA ? "image/png" : "image/jpeg", + 1 + ) + ); + const arrayBuffer = await blob.arrayBuffer(); + zip.file(`textures/Texture_${id}.${isRGBA ? "png" : "jpg"}`, arrayBuffer); + } + + // 64 byte alignment + let offset = 0; + + zip.forEach(async (relativePath: string) => { + const headerSize = 34 + relativePath.length; + offset += headerSize; + const offsetMod64 = offset & 63; + const content = await zip.file(relativePath)?.async("uint8array"); + + if (!content) { + return; + } + + if (offsetMod64 !== 4) { + const padLength = 64 - offsetMod64; + const padding = new Uint8Array(padLength); + const newContent = new Uint8Array(content.length + padLength); + newContent.set(content, 0); + newContent.set(padding, content.length); + zip.file(relativePath, newContent); + } + + offset += content.length; + }); + + const zipBlob = await zip.generateAsync({ + type: "blob", + compression: "STORE", + }); + return new Uint8Array(await zipBlob.arrayBuffer()); + } +} + +function imageToCanvas( + image: HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap, + color?: string +): HTMLCanvasElement { + if ( + (typeof HTMLImageElement !== "undefined" && + image instanceof HTMLImageElement) || + (typeof HTMLCanvasElement !== "undefined" && + image instanceof HTMLCanvasElement) || + (typeof OffscreenCanvas !== "undefined" && + image instanceof OffscreenCanvas) || + (typeof ImageBitmap !== "undefined" && image instanceof ImageBitmap) + ) { + const scale = 1024 / Math.max(image.width, image.height); + const canvas = document.createElement("canvas"); + canvas.width = image.width * Math.min(1, scale); + canvas.height = image.height * Math.min(1, scale); + const context = canvas.getContext("2d")!; + + // Use nearest neighbor for scaling + context.imageSmoothingEnabled = false; + + context.drawImage(image, 0, 0, canvas.width, canvas.height); + + if (color !== undefined) { + const hex = parseInt(color, 16); + const r = ((hex >> 16) & 255) / 255; + const g = ((hex >> 8) & 255) / 255; + const b = (hex & 255) / 255; + const imagedata = context.getImageData(0, 0, canvas.width, canvas.height); + const data = imagedata.data; + + for (let i = 0; i < data.length; i += 4) { + data[i + 0] = data[i + 0] * r; + data[i + 1] = data[i + 1] * g; + data[i + 2] = data[i + 2] * b; + } + + context.putImageData(imagedata, 0, 0); + } + + return canvas; + } + + throw new Error("Unsupported image type"); +} + +const PRECISION = 7; + +function buildHeader(): string { + return `#usda 1.0 +( + customLayerData = { + string creator = "Blockbench USDZExporter" + } + metersPerUnit = 1 + upAxis = "Y" +) + +`; +} + +function buildUSDFileAsString(dataToInsert: string): string { + let output = buildHeader(); + output += dataToInsert; + return output; +} + +function buildXform( + object: THREE.Object3D, + geometry: THREE.BufferGeometry, + material: THREE.Material +): string { + const name = "Object_" + object.id; + const transform = buildMatrix(object.matrixWorld); + + if (object.matrixWorld.determinant() < 0) { + console.warn( + "THREE.USDZExporter: USDZ does not support negative scales", + object + ); + } + + return `def Xform "${name}" ( + prepend references = @./geometries/Geometry_${geometry.id}.usd@ +) +{ + matrix4d xformOp:transform = ${transform} + uniform token[] xformOpOrder = ["xformOp:transform"] + + rel material:binding = +} + +`; +} + +function buildMatrix(matrix: THREE.Matrix4): string { + const array = matrix.elements; + return `( ${buildMatrixRow(array, 0)}, ${buildMatrixRow(array, 4)}, ${buildMatrixRow(array, 8)}, ${buildMatrixRow(array, 12)} )`; +} + +function buildMatrixRow(array: number[], offset: number): string { + return `(${array[offset + 0]}, ${array[offset + 1]}, ${array[offset + 2]}, ${array[offset + 3]})`; +} + +function buildMeshObject(geometry: THREE.BufferGeometry): string { + const mesh = buildMesh(geometry); + return ` +def "Geometry" +{ + ${mesh} +} +`; +} + +function buildMesh(geometry: THREE.BufferGeometry): string { + const name = "Geometry"; + const attributes = geometry.attributes; + const count = attributes.position.count; + return ` + def Mesh "${name}" + { + int[] faceVertexCounts = [${buildMeshVertexCount(geometry)}] + int[] faceVertexIndices = [${buildMeshVertexIndices(geometry)}] + normal3f[] normals = [${buildVector3Array(attributes.normal as THREE.BufferAttribute, count)}] ( + interpolation = "vertex" + ) + point3f[] points = [${buildVector3Array(attributes.position as THREE.BufferAttribute, count)}] + float2[] primvars:st = [${buildVector2Array(attributes.uv as THREE.BufferAttribute, count)}] ( + interpolation = "vertex" + ) + uniform token subdivisionScheme = "none" + } +`; +} + +function buildMeshVertexCount(geometry: THREE.BufferGeometry): string { + const count = + geometry.index !== null + ? geometry.index.count + : geometry.attributes.position.count; + return Array(count / 3) + .fill(3) + .join(", "); +} + +function buildMeshVertexIndices(geometry: THREE.BufferGeometry): string { + const index = geometry.index; + const array: number[] = []; + + if (index !== null) { + for (let i = 0; i < index.count; i++) { + array.push(index.getX(i)); + } + } else { + const length = geometry.attributes.position.count; + + for (let i = 0; i < length; i++) { + array.push(i); + } + } + + return array.join(", "); +} + +function buildVector3Array( + attribute: THREE.BufferAttribute, + count: number +): string { + if (attribute === undefined) { + console.warn("USDZExporter: Normals missing."); + return Array(count).fill("(0, 0, 0)").join(", "); + } + + const array: string[] = []; + + for (let i = 0; i < attribute.count; i++) { + const x = attribute.getX(i); + const y = attribute.getY(i); + const z = attribute.getZ(i); + array.push( + `(${x.toPrecision(PRECISION)}, ${y.toPrecision(PRECISION)}, ${z.toPrecision(PRECISION)})` + ); + } + + return array.join(", "); +} + +function buildVector2Array( + attribute: THREE.BufferAttribute, + count: number +): string { + if (attribute === undefined) { + console.warn("USDZExporter: UVs missing."); + return Array(count).fill("(0, 0)").join(", "); + } + + const array: string[] = []; + + for (let i = 0; i < attribute.count; i++) { + const x = attribute.getX(i); + const y = attribute.getY(i); + array.push( + `(${x.toPrecision(PRECISION)}, ${Number(y.toPrecision(PRECISION))})` + ); + } + return array.join(", "); +} + +function buildMaterials( + materials: { [key: string]: THREE.Material }, + textures: { [key: string]: THREE.Texture } +): string { + const array: string[] = []; + + for (const uuid in materials) { + const material = materials[uuid]; + array.push(buildMaterial(material, textures)); + } + + return `def "Materials" +{ +${array.join("")} +} + +`; +} + +function buildMaterial( + material: THREE.Material, + textures: { [key: string]: THREE.Texture } +): string { + // https://graphics.pixar.com/usd/docs/UsdPreviewSurface-Proposal.html + const pad = " "; + const inputs: string[] = []; + const samplers: string[] = []; + + function buildTexture( + texture: THREE.Texture, + mapType: string, + color?: THREE.Color + ): string { + const id = texture.id + (color ? "_" + color.getHexString() : ""); + const isRGBA = texture.format === THREE.RGBAFormat; + textures[id] = texture; + return ` + def Shader "Transform2d_${mapType}" ( + sdrMetadata = { + string role = "math" + } + ) + { + uniform token info:id = "UsdTransform2d" + float2 inputs:in.connect = + float2 inputs:scale = ${buildVector2(texture.repeat)} + float2 inputs:translation = ${buildVector2(texture.offset)} + float2 outputs:result + } + + def Shader "Texture_${texture.id}_${mapType}" + { + uniform token info:id = "UsdUVTexture" + asset inputs:file = @textures/Texture_${id}.${isRGBA ? "png" : "jpg"}@ + float2 inputs:st.connect = + token inputs:wrapS = "repeat" + token inputs:wrapT = "repeat" + float outputs:r + float outputs:g + float outputs:b + float3 outputs:rgb + }`; + } + + const mat = material as THREE.MeshStandardMaterial; + + if (mat.map !== null) { + inputs.push( + `${pad}color3f inputs:diffuseColor.connect = ` + ); + samplers.push(buildTexture(mat.map, "diffuse", mat.color)); + } else { + inputs.push(`${pad}color3f inputs:diffuseColor = ${buildColor(mat.color)}`); + } + + if (mat.emissiveMap !== null) { + inputs.push( + `${pad}color3f inputs:emissiveColor.connect = ` + ); + samplers.push(buildTexture(mat.emissiveMap, "emissive")); + } else if (mat.emissive.getHex() > 0) { + inputs.push( + `${pad}color3f inputs:emissiveColor = ${buildColor(mat.emissive)}` + ); + } + + if (mat.normalMap !== null) { + inputs.push( + `${pad}normal3f inputs:normal.connect = ` + ); + samplers.push(buildTexture(mat.normalMap, "normal")); + } + + if (mat.aoMap !== null) { + inputs.push( + `${pad}float inputs:occlusion.connect = ` + ); + samplers.push(buildTexture(mat.aoMap, "occlusion")); + } + + if (mat.roughnessMap !== null && mat.roughness === 1) { + inputs.push( + `${pad}float inputs:roughness.connect = ` + ); + samplers.push(buildTexture(mat.roughnessMap, "roughness")); + } else { + inputs.push(`${pad}float inputs:roughness = ${mat.roughness}`); + } + + if (mat.metalnessMap !== null && mat.metalness === 1) { + inputs.push( + `${pad}float inputs:metallic.connect = ` + ); + samplers.push(buildTexture(mat.metalnessMap, "metallic")); + } else { + inputs.push(`${pad}float inputs:metallic = ${mat.metalness}`); + } + + if (mat.alphaMap !== null) { + inputs.push( + `${pad}float inputs:opacity.connect = ` + ); + inputs.push(`${pad}float inputs:opacityThreshold = 0.0001`); + samplers.push(buildTexture(mat.alphaMap, "opacity")); + } else { + inputs.push(`${pad}float inputs:opacity = ${mat.opacity}`); + } + + if ((mat as any).isMeshPhysicalMaterial) { + const physicalMat = mat as any; + inputs.push(`${pad}float inputs:clearcoat = ${physicalMat.clearcoat}`); + inputs.push( + `${pad}float inputs:clearcoatRoughness = ${physicalMat.clearcoatRoughness}` + ); + inputs.push(`${pad}float inputs:ior = ${physicalMat.ior}`); + } + + return ` + def Material "Material_${mat.id}" + { + def Shader "PreviewSurface" + { + uniform token info:id = "UsdPreviewSurface" +${inputs.join("\n")} + int inputs:useSpecularWorkflow = 0 + token outputs:surface + } + + token outputs:surface.connect = + token inputs:frame:stPrimvarName = "st" + + def Shader "uvReader_st" + { + uniform token info:id = "UsdPrimvarReader_float2" + token inputs:varname.connect = + float2 inputs:fallback = (0.0, 0.0) + float2 outputs:result + } + +${samplers.join("\n")} + + } +`; +} + +function buildColor(color: THREE.Color): string { + return `(${color.r}, ${color.g}, ${color.b})`; +} + +function buildVector2(vector: THREE.Vector2): string { + return `(${vector.x}, ${vector.y})`; +} + +export default USDZExporter; diff --git a/src/usd_codec/src/io/formats/usdz/index.ts b/src/usd_codec/src/io/formats/usdz/index.ts new file mode 100644 index 00000000..0b532e04 --- /dev/null +++ b/src/usd_codec/src/io/formats/usdz/index.ts @@ -0,0 +1,78 @@ +import USDZExporter from "./USDZExporter"; + +function getOutputBaseName() { + if (!Project) { + // @ts-ignore Blockbench globals + return pathToName(Texture.selected?.name ?? "texture"); + } + + return Project.model_identifier.length > 0 + ? Project.model_identifier + : Project.getDisplayName(); +} + +export function setup() { + const usdz = new Codec("usdz", { + extension: "usdz", + name: "USDZ", + remember: true, + fileName() { + return getOutputBaseName() + ".usdz"; + }, + async compile(compileOptions = {}) { + if (!Project) { + throw new Error("No project loaded"); + } + const options = Object.assign(this.export_options ?? {}, compileOptions); + + const exporter = new USDZExporter(); + const scene = new window.THREE.Scene(); + + scene.name = "blockbench_export"; + scene.add(Project.model_3d); + + const result = await exporter.parse(scene); + + this.dispatchEvent("compile", { model: result, options }); + + Canvas.scene.add(Project.model_3d); + + return result; + }, + async export(options = {}) { + const content = await this.compile(options); + Blockbench.export( + { + content, + name: this.fileName(), + startpath: this.startPath(), + resource_id: "usdz", + type: this.name, + extensions: ["usdz"], + savetype: "buffer", + }, + (path) => this.afterDownload(path) + ); + }, + }); + + const exportUsdz = new Action("export_usdz", { + category: "file", + name: "Export USDZ", + description: "Exports the current model as a USDZ file", + icon: "stacks", + async click() { + if (!usdz) { + return; + } + + usdz.export(); + }, + }); + + MenuBar.addAction(exportUsdz, "file.export"); +} + +export function teardown() { + MenuBar.removeAction("file.export.export_usdz"); +} \ No newline at end of file diff --git a/src/usd_codec/tsconfig.json b/src/usd_codec/tsconfig.json new file mode 100644 index 00000000..a88db12f --- /dev/null +++ b/src/usd_codec/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": [ + "ES2022", + "DOM" + ], + "target": "ES2022", + "module": "ES2022", + "moduleDetection": "force", + "allowJs": true, + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} \ No newline at end of file From dc02c82b7f0185ad34dcaf160b228e9538f9f70e Mon Sep 17 00:00:00 2001 From: Jason Gardner Date: Sat, 18 Jan 2025 11:39:26 -0600 Subject: [PATCH 2/5] upd: Add about / license --- plugins/usd_codec/LICENSE.md | 674 +++++++++++++++++++++++++++++++++++ plugins/usd_codec/about.md | 4 + 2 files changed, 678 insertions(+) create mode 100644 plugins/usd_codec/LICENSE.md create mode 100644 plugins/usd_codec/about.md diff --git a/plugins/usd_codec/LICENSE.md b/plugins/usd_codec/LICENSE.md new file mode 100644 index 00000000..e72bfdda --- /dev/null +++ b/plugins/usd_codec/LICENSE.md @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/plugins/usd_codec/about.md b/plugins/usd_codec/about.md new file mode 100644 index 00000000..709f1063 --- /dev/null +++ b/plugins/usd_codec/about.md @@ -0,0 +1,4 @@ +# USD Codec + +Plugin features: +- Export project in [USDZ](https://openusd.org/release/index.html) format. \ No newline at end of file From 0c0b0580af38191ea25ce19cafd777091e13f495 Mon Sep 17 00:00:00 2001 From: Jason Gardner Date: Sat, 18 Jan 2025 12:18:52 -0600 Subject: [PATCH 3/5] chore: Minify output / Add action cleanup --- plugins/usd_codec/about.md | 3 +- plugins/usd_codec/icon.png | Bin 13039 -> 0 bytes plugins/usd_codec/members.yml | 4 + plugins/usd_codec/usd_codec.js | 388 ++------------------- src/usd_codec/src/index.ts | 4 +- src/usd_codec/src/io/formats/usdz/index.ts | 5 +- 6 files changed, 43 insertions(+), 361 deletions(-) delete mode 100644 plugins/usd_codec/icon.png create mode 100644 plugins/usd_codec/members.yml diff --git a/plugins/usd_codec/about.md b/plugins/usd_codec/about.md index 709f1063..e3bdd5e4 100644 --- a/plugins/usd_codec/about.md +++ b/plugins/usd_codec/about.md @@ -1,4 +1,5 @@ # USD Codec Plugin features: -- Export project in [USDZ](https://openusd.org/release/index.html) format. \ No newline at end of file +- Export project in [USDZ](https://openusd.org/release/index.html) format. +- Supports PBR textures in USD output. \ No newline at end of file diff --git a/plugins/usd_codec/icon.png b/plugins/usd_codec/icon.png deleted file mode 100644 index 7c303d0f4ae41a9778befc2941abfb930e1b6bca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13039 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 diff --git a/plugins/usd_codec/members.yml b/plugins/usd_codec/members.yml new file mode 100644 index 00000000..438a1942 --- /dev/null +++ b/plugins/usd_codec/members.yml @@ -0,0 +1,4 @@ +maintainers: + - jasonjgardner +developers: + - jasonjgardner diff --git a/plugins/usd_codec/usd_codec.js b/plugins/usd_codec/usd_codec.js index 95f61e17..b13a6c99 100644 --- a/plugins/usd_codec/usd_codec.js +++ b/plugins/usd_codec/usd_codec.js @@ -1,101 +1,4 @@ -// src/io/formats/usdz/USDZExporter.ts -class USDZExporter { - async parse(scene) { - const zip = new JSZip; - const modelFileName = "model.usda"; - zip.file(modelFileName, ""); - let output = buildHeader(); - const materials = {}; - const textures = {}; - scene.traverseVisible((object) => { - if (!object.isMesh) { - return; - } - const mesh = object; - if (!mesh.material.isMeshStandardMaterial) { - console.warn("THREE.USDZExporter: Unsupported material type (USDZ only supports MeshStandardMaterial)", object); - return; - } - const geometry = mesh.geometry; - const material = mesh.material; - const geometryFileName = "geometries/Geometry_" + geometry.id + ".usd"; - if (!zip.file(geometryFileName)) { - const meshObject = buildMeshObject(geometry); - zip.file(geometryFileName, buildUSDFileAsString(meshObject)); - } - if (!(material.uuid in materials)) { - materials[material.uuid] = material; - } - output += buildXform(mesh, geometry, material); - }); - output += buildMaterials(materials, textures); - zip.file(modelFileName, output); - output = null; - for (const id in textures) { - const texture = textures[id]; - const color = id.split("_")[1]; - const isRGBA = texture.format === THREE.RGBAFormat; - const canvas = imageToCanvas(texture.image, color); - const blob = await new Promise((resolve) => canvas.toBlob((v) => v && resolve(v), isRGBA ? "image/png" : "image/jpeg", 1)); - const arrayBuffer = await blob.arrayBuffer(); - zip.file(`textures/Texture_${id}.${isRGBA ? "png" : "jpg"}`, arrayBuffer); - } - let offset = 0; - zip.forEach(async (relativePath) => { - const headerSize = 34 + relativePath.length; - offset += headerSize; - const offsetMod64 = offset & 63; - const content = await zip.file(relativePath)?.async("uint8array"); - if (!content) { - return; - } - if (offsetMod64 !== 4) { - const padLength = 64 - offsetMod64; - const padding = new Uint8Array(padLength); - const newContent = new Uint8Array(content.length + padLength); - newContent.set(content, 0); - newContent.set(padding, content.length); - zip.file(relativePath, newContent); - } - offset += content.length; - }); - const zipBlob = await zip.generateAsync({ - type: "blob", - compression: "STORE" - }); - return new Uint8Array(await zipBlob.arrayBuffer()); - } -} -function imageToCanvas(image, color) { - if (typeof HTMLImageElement !== "undefined" && image instanceof HTMLImageElement || typeof HTMLCanvasElement !== "undefined" && image instanceof HTMLCanvasElement || typeof OffscreenCanvas !== "undefined" && image instanceof OffscreenCanvas || typeof ImageBitmap !== "undefined" && image instanceof ImageBitmap) { - const scale = 1024 / Math.max(image.width, image.height); - const canvas = document.createElement("canvas"); - canvas.width = image.width * Math.min(1, scale); - canvas.height = image.height * Math.min(1, scale); - const context = canvas.getContext("2d"); - context.imageSmoothingEnabled = false; - context.drawImage(image, 0, 0, canvas.width, canvas.height); - if (color !== undefined) { - const hex = parseInt(color, 16); - const r = (hex >> 16 & 255) / 255; - const g = (hex >> 8 & 255) / 255; - const b = (hex & 255) / 255; - const imagedata = context.getImageData(0, 0, canvas.width, canvas.height); - const data = imagedata.data; - for (let i = 0;i < data.length; i += 4) { - data[i + 0] = data[i + 0] * r; - data[i + 1] = data[i + 1] * g; - data[i + 2] = data[i + 2] * b; - } - context.putImageData(imagedata, 0, 0); - } - return canvas; - } - throw new Error("Unsupported image type"); -} -var PRECISION = 7; -function buildHeader() { - return `#usda 1.0 +class E{async parse(_){let q=new JSZip,K="model.usda";q.file("model.usda","");let k=j(),D={},W={};_.traverseVisible(($)=>{if(!$.isMesh)return;let H=$;if(!H.material.isMeshStandardMaterial){console.warn("THREE.USDZExporter: Unsupported material type (USDZ only supports MeshStandardMaterial)",$);return}let{geometry:Y,material:Q}=H,G="geometries/Geometry_"+Y.id+".usd";if(!q.file(G)){let F=z(Y);q.file(G,R(F))}if(!(Q.uuid in D))D[Q.uuid]=Q;k+=M(H,Y,Q)}),k+=d(D,W),q.file("model.usda",k),k=null;for(let $ in W){let H=W[$],Y=$.split("_")[1],Q=H.format===THREE.RGBAFormat,G=P(H.image,Y),L=await(await new Promise((I)=>G.toBlob((A)=>A&&I(A),Q?"image/png":"image/jpeg",1))).arrayBuffer();q.file(`textures/Texture_${$}.${Q?"png":"jpg"}`,L)}let J=0;q.forEach(async($)=>{let H=34+$.length;J+=H;let Y=J&63,Q=await q.file($)?.async("uint8array");if(!Q)return;if(Y!==4){let G=64-Y,F=new Uint8Array(G),L=new Uint8Array(Q.length+G);L.set(Q,0),L.set(F,Q.length),q.file($,L)}J+=Q.length});let X=await q.generateAsync({type:"blob",compression:"STORE"});return new Uint8Array(await X.arrayBuffer())}}function P(_,q){if(typeof HTMLImageElement!=="undefined"&&_ instanceof HTMLImageElement||typeof HTMLCanvasElement!=="undefined"&&_ instanceof HTMLCanvasElement||typeof OffscreenCanvas!=="undefined"&&_ instanceof OffscreenCanvas||typeof ImageBitmap!=="undefined"&&_ instanceof ImageBitmap){let K=1024/Math.max(_.width,_.height),k=document.createElement("canvas");k.width=_.width*Math.min(1,K),k.height=_.height*Math.min(1,K);let D=k.getContext("2d");if(D.imageSmoothingEnabled=!1,D.drawImage(_,0,0,k.width,k.height),q!==void 0){let W=parseInt(q,16),J=(W>>16&255)/255,X=(W>>8&255)/255,$=(W&255)/255,H=D.getImageData(0,0,k.width,k.height),Y=H.data;for(let Q=0;Q +`}function R(_){let q=j();return q+=_,q}function M(_,q,K){let k="Object_"+_.id,D=T(_.matrixWorld);if(_.matrixWorld.determinant()<0)console.warn("THREE.USDZExporter: USDZ does not support negative scales",_);return`def Xform "${k}" ( + prepend references = @./geometries/Geometry_${q.id}.usd@ ) { - matrix4d xformOp:transform = ${transform} + matrix4d xformOp:transform = ${D} uniform token[] xformOpOrder = ["xformOp:transform"] - rel material:binding = + rel material:binding = } -`; -} -function buildMatrix(matrix) { - const array = matrix.elements; - return `( ${buildMatrixRow(array, 0)}, ${buildMatrixRow(array, 4)}, ${buildMatrixRow(array, 8)}, ${buildMatrixRow(array, 12)} )`; -} -function buildMatrixRow(array, offset) { - return `(${array[offset + 0]}, ${array[offset + 1]}, ${array[offset + 2]}, ${array[offset + 3]})`; -} -function buildMeshObject(geometry) { - const mesh = buildMesh(geometry); - return ` +`}function T(_){let q=_.elements;return`( ${Z(q,0)}, ${Z(q,4)}, ${Z(q,8)}, ${Z(q,12)} )`}function Z(_,q){return`(${_[q+0]}, ${_[q+1]}, ${_[q+2]}, ${_[q+3]})`}function z(_){return` def "Geometry" { - ${mesh} + ${h(_)} } -`; -} -function buildMesh(geometry) { - const name = "Geometry"; - const attributes = geometry.attributes; - const count = attributes.position.count; - return ` - def Mesh "${name}" +`}function h(_){let K=_.attributes,k=K.position.count;return` + def Mesh "Geometry" { - int[] faceVertexCounts = [${buildMeshVertexCount(geometry)}] - int[] faceVertexIndices = [${buildMeshVertexIndices(geometry)}] - normal3f[] normals = [${buildVector3Array(attributes.normal, count)}] ( + int[] faceVertexCounts = [${v(_)}] + int[] faceVertexIndices = [${f(_)}] + normal3f[] normals = [${U(K.normal,k)}] ( interpolation = "vertex" ) - point3f[] points = [${buildVector3Array(attributes.position, count)}] - float2[] primvars:st = [${buildVector2Array(attributes.uv, count)}] ( + point3f[] points = [${U(K.position,k)}] + float2[] primvars:st = [${g(K.uv,k)}] ( interpolation = "vertex" ) uniform token subdivisionScheme = "none" } -`; -} -function buildMeshVertexCount(geometry) { - const count = geometry.index !== null ? geometry.index.count : geometry.attributes.position.count; - return Array(count / 3).fill(3).join(", "); -} -function buildMeshVertexIndices(geometry) { - const index = geometry.index; - const array = []; - if (index !== null) { - for (let i = 0;i < index.count; i++) { - array.push(index.getX(i)); - } - } else { - const length = geometry.attributes.position.count; - for (let i = 0;i < length; i++) { - array.push(i); - } - } - return array.join(", "); -} -function buildVector3Array(attribute, count) { - if (attribute === undefined) { - console.warn("USDZExporter: Normals missing."); - return Array(count).fill("(0, 0, 0)").join(", "); - } - const array = []; - for (let i = 0;i < attribute.count; i++) { - const x = attribute.getX(i); - const y = attribute.getY(i); - const z = attribute.getZ(i); - array.push(`(${x.toPrecision(PRECISION)}, ${y.toPrecision(PRECISION)}, ${z.toPrecision(PRECISION)})`); - } - return array.join(", "); -} -function buildVector2Array(attribute, count) { - if (attribute === undefined) { - console.warn("USDZExporter: UVs missing."); - return Array(count).fill("(0, 0)").join(", "); - } - const array = []; - for (let i = 0;i < attribute.count; i++) { - const x = attribute.getX(i); - const y = attribute.getY(i); - array.push(`(${x.toPrecision(PRECISION)}, ${Number(y.toPrecision(PRECISION))})`); - } - return array.join(", "); -} -function buildMaterials(materials, textures) { - const array = []; - for (const uuid in materials) { - const material = materials[uuid]; - array.push(buildMaterial(material, textures)); - } - return `def "Materials" +`}function v(_){let q=_.index!==null?_.index.count:_.attributes.position.count;return Array(q/3).fill(3).join(", ")}function f(_){let q=_.index,K=[];if(q!==null)for(let k=0;k - float2 inputs:scale = ${buildVector2(texture.repeat)} - float2 inputs:translation = ${buildVector2(texture.offset)} + float2 inputs:in.connect = + float2 inputs:scale = ${N(X.repeat)} + float2 inputs:translation = ${N(X.offset)} float2 outputs:result } - def Shader "Texture_${texture.id}_${mapType}" + def Shader "Texture_${X.id}_${$}" { uniform token info:id = "UsdUVTexture" - asset inputs:file = @textures/Texture_${id}.${isRGBA ? "png" : "jpg"}@ - float2 inputs:st.connect = + asset inputs:file = @textures/Texture_${Y}.${Q?"png":"jpg"}@ + float2 inputs:st.connect = token inputs:wrapS = "repeat" token inputs:wrapT = "repeat" float outputs:r float outputs:g float outputs:b float3 outputs:rgb - }`; - } - const mat = material; - if (mat.map !== null) { - inputs.push(`${pad}color3f inputs:diffuseColor.connect = `); - samplers.push(buildTexture(mat.map, "diffuse", mat.color)); - } else { - inputs.push(`${pad}color3f inputs:diffuseColor = ${buildColor(mat.color)}`); - } - if (mat.emissiveMap !== null) { - inputs.push(`${pad}color3f inputs:emissiveColor.connect = `); - samplers.push(buildTexture(mat.emissiveMap, "emissive")); - } else if (mat.emissive.getHex() > 0) { - inputs.push(`${pad}color3f inputs:emissiveColor = ${buildColor(mat.emissive)}`); - } - if (mat.normalMap !== null) { - inputs.push(`${pad}normal3f inputs:normal.connect = `); - samplers.push(buildTexture(mat.normalMap, "normal")); - } - if (mat.aoMap !== null) { - inputs.push(`${pad}float inputs:occlusion.connect = `); - samplers.push(buildTexture(mat.aoMap, "occlusion")); - } - if (mat.roughnessMap !== null && mat.roughness === 1) { - inputs.push(`${pad}float inputs:roughness.connect = `); - samplers.push(buildTexture(mat.roughnessMap, "roughness")); - } else { - inputs.push(`${pad}float inputs:roughness = ${mat.roughness}`); - } - if (mat.metalnessMap !== null && mat.metalness === 1) { - inputs.push(`${pad}float inputs:metallic.connect = `); - samplers.push(buildTexture(mat.metalnessMap, "metallic")); - } else { - inputs.push(`${pad}float inputs:metallic = ${mat.metalness}`); - } - if (mat.alphaMap !== null) { - inputs.push(`${pad}float inputs:opacity.connect = `); - inputs.push(`${pad}float inputs:opacityThreshold = 0.0001`); - samplers.push(buildTexture(mat.alphaMap, "opacity")); - } else { - inputs.push(`${pad}float inputs:opacity = ${mat.opacity}`); - } - if (mat.isMeshPhysicalMaterial) { - const physicalMat = mat; - inputs.push(`${pad}float inputs:clearcoat = ${physicalMat.clearcoat}`); - inputs.push(`${pad}float inputs:clearcoatRoughness = ${physicalMat.clearcoatRoughness}`); - inputs.push(`${pad}float inputs:ior = ${physicalMat.ior}`); - } - return ` - def Material "Material_${mat.id}" + }`}let J=_;if(J.map!==null)k.push(` color3f inputs:diffuseColor.connect = `),D.push(W(J.map,"diffuse",J.color));else k.push(` color3f inputs:diffuseColor = ${V(J.color)}`);if(J.emissiveMap!==null)k.push(` color3f inputs:emissiveColor.connect = `),D.push(W(J.emissiveMap,"emissive"));else if(J.emissive.getHex()>0)k.push(` color3f inputs:emissiveColor = ${V(J.emissive)}`);if(J.normalMap!==null)k.push(` normal3f inputs:normal.connect = `),D.push(W(J.normalMap,"normal"));if(J.aoMap!==null)k.push(` float inputs:occlusion.connect = `),D.push(W(J.aoMap,"occlusion"));if(J.roughnessMap!==null&&J.roughness===1)k.push(` float inputs:roughness.connect = `),D.push(W(J.roughnessMap,"roughness"));else k.push(` float inputs:roughness = ${J.roughness}`);if(J.metalnessMap!==null&&J.metalness===1)k.push(` float inputs:metallic.connect = `),D.push(W(J.metalnessMap,"metallic"));else k.push(` float inputs:metallic = ${J.metalness}`);if(J.alphaMap!==null)k.push(` float inputs:opacity.connect = `),k.push(" float inputs:opacityThreshold = 0.0001"),D.push(W(J.alphaMap,"opacity"));else k.push(` float inputs:opacity = ${J.opacity}`);if(J.isMeshPhysicalMaterial){let X=J;k.push(` float inputs:clearcoat = ${X.clearcoat}`),k.push(` float inputs:clearcoatRoughness = ${X.clearcoatRoughness}`),k.push(` float inputs:ior = ${X.ior}`)}return` + def Material "Material_${J.id}" { def Shader "PreviewSurface" { uniform token info:id = "UsdPreviewSurface" -${inputs.join(` +${k.join(` `)} int inputs:useSpecularWorkflow = 0 token outputs:surface } - token outputs:surface.connect = + token outputs:surface.connect = token inputs:frame:stPrimvarName = "st" def Shader "uvReader_st" { uniform token info:id = "UsdPrimvarReader_float2" - token inputs:varname.connect = + token inputs:varname.connect = float2 inputs:fallback = (0.0, 0.0) float2 outputs:result } -${samplers.join(` +${D.join(` `)} } -`; -} -function buildColor(color) { - return `(${color.r}, ${color.g}, ${color.b})`; -} -function buildVector2(vector) { - return `(${vector.x}, ${vector.y})`; -} -var USDZExporter_default = USDZExporter; - -// src/io/formats/usdz/index.ts -function getOutputBaseName() { - if (!Project) { - return pathToName(Texture.selected?.name ?? "texture"); - } - return Project.model_identifier.length > 0 ? Project.model_identifier : Project.getDisplayName(); -} -function setup() { - const usdz = new Codec("usdz", { - extension: "usdz", - name: "USDZ", - remember: true, - fileName() { - return getOutputBaseName() + ".usdz"; - }, - async compile(compileOptions = {}) { - if (!Project) { - throw new Error("No project loaded"); - } - const options = Object.assign(this.export_options ?? {}, compileOptions); - const exporter = new USDZExporter_default; - const scene = new window.THREE.Scene; - scene.name = "blockbench_export"; - scene.add(Project.model_3d); - const result = await exporter.parse(scene); - this.dispatchEvent("compile", { model: result, options }); - Canvas.scene.add(Project.model_3d); - return result; - }, - async export(options = {}) { - const content = await this.compile(options); - Blockbench.export({ - content, - name: this.fileName(), - startpath: this.startPath(), - resource_id: "usdz", - type: this.name, - extensions: ["usdz"], - savetype: "buffer" - }, (path) => this.afterDownload(path)); - } - }); - const exportUsdz = new Action("export_usdz", { - category: "file", - name: "Export USDZ", - description: "Exports the current model as a USDZ file", - icon: "stacks", - async click() { - if (!usdz) { - return; - } - usdz.export(); - } - }); - MenuBar.addAction(exportUsdz, "file.export"); -} -function teardown() { - MenuBar.removeAction("file.export.export_usdz"); -} - -// src/index.ts -(() => { - BBPlugin.register("usd_codec", { - version: "1.0.0", - title: "USD Codec", - author: "Jason J. Gardner", - description: "Export Universal Scene Descriptor (USD) files for use in 3D applications like Blender, Maya, and Houdini.", - tags: ["Codec", "PBR"], - icon: "icon.png", - variant: "both", - await_loading: true, - repository: "https://github.com/jasonjgardner/blockbench-plugins", - has_changelog: false, - min_version: "4.12.1", - onload: setup, - onunload: teardown - }); -})(); +`}function V(_){return`(${_.r}, ${_.g}, ${_.b})`}function N(_){return`(${_.x}, ${_.y})`}var O=E;var w=null;function y(){if(!Project)return pathToName(Texture.selected?.name??"texture");return Project.model_identifier.length>0?Project.model_identifier:Project.getDisplayName()}function C(){let _=new Codec("usdz",{extension:"usdz",name:"USDZ",remember:!0,fileName(){return y()+".usdz"},async compile(q={}){if(!Project)throw new Error("No project loaded");let K=Object.assign(this.export_options??{},q),k=new O,D=new window.THREE.Scene;D.name="blockbench_export",D.add(Project.model_3d);let W=await k.parse(D);return this.dispatchEvent("compile",{model:W,options:K}),Canvas.scene.add(Project.model_3d),W},async export(q={}){let K=await this.compile(q);Blockbench.export({content:K,name:this.fileName(),startpath:this.startPath(),resource_id:"usdz",type:this.name,extensions:["usdz"],savetype:"buffer"},(k)=>this.afterDownload(k))}});w=new Action("export_usdz",{category:"file",name:"Export USDZ",description:"Exports the current model as a USDZ file",icon:"stacks",async click(){if(!_)return;_.export()}}),MenuBar.addAction(w,"file.export")}function S(){MenuBar.removeAction("file.export.export_usdz"),w?.delete()}(()=>{BBPlugin.register("usd_codec",{version:"1.0.0",title:"USD Codec",author:"Jason J. Gardner",description:"Export Universal Scene Descriptor (USD) files for use in 3D applications like Blender, Maya, Houdini, NVIDIA Omniverse, and more.",tags:["Codec","PBR"],icon:"icon.svg",variant:"both",await_loading:!0,repository:"https://github.com/jasonjgardner/blockbench-plugins",has_changelog:!1,min_version:"4.12.1",onload:C,onunload:S})})(); diff --git a/src/usd_codec/src/index.ts b/src/usd_codec/src/index.ts index facc8553..b99dd4b2 100644 --- a/src/usd_codec/src/index.ts +++ b/src/usd_codec/src/index.ts @@ -14,9 +14,9 @@ import { setup, teardown } from './io/formats/usdz' title: "USD Codec", author: "Jason J. Gardner", description: - "Export Universal Scene Descriptor (USD) files for use in 3D applications like Blender, Maya, and Houdini.", + "Export Universal Scene Descriptor (USD) files for use in 3D applications like Blender, Maya, Houdini, NVIDIA Omniverse, and more.", tags: ["Codec","PBR"], - icon: "icon.png", + icon: "icon.svg", variant: "both", await_loading: true, repository: "https://github.com/jasonjgardner/blockbench-plugins", diff --git a/src/usd_codec/src/io/formats/usdz/index.ts b/src/usd_codec/src/io/formats/usdz/index.ts index 0b532e04..e150653e 100644 --- a/src/usd_codec/src/io/formats/usdz/index.ts +++ b/src/usd_codec/src/io/formats/usdz/index.ts @@ -1,5 +1,7 @@ import USDZExporter from "./USDZExporter"; +let exportUsdz: Action | null = null; + function getOutputBaseName() { if (!Project) { // @ts-ignore Blockbench globals @@ -56,7 +58,7 @@ export function setup() { }, }); - const exportUsdz = new Action("export_usdz", { + exportUsdz = new Action("export_usdz", { category: "file", name: "Export USDZ", description: "Exports the current model as a USDZ file", @@ -75,4 +77,5 @@ export function setup() { export function teardown() { MenuBar.removeAction("file.export.export_usdz"); + exportUsdz?.delete(); } \ No newline at end of file From de4829b5e67596a33350fabb943a01a20943626a Mon Sep 17 00:00:00 2001 From: Jason Gardner Date: Sat, 18 Jan 2025 12:32:56 -0600 Subject: [PATCH 4/5] upd: Add USD Codec to plugins list --- plugins.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plugins.json b/plugins.json index da9c567a..99461707 100644 --- a/plugins.json +++ b/plugins.json @@ -1060,5 +1060,22 @@ "has_changelog": true, "min_version": "4.10.4", "repository": "https://github.com/Shadowkitten47/Meshy" + }, + "usd_codec": { + "title": "USD Codec", + "author": "Jason J. Gardner", + "await_loading": true, + "creation_date": "2025-01-18", + "icon": "icon.svg", + "description": "Export Universal Scene Descriptor (USD) files for use in 3D applications like Blender, Maya, Houdini, NVIDIA Omniverse, and more.", + "has_changelog": false, + "website": "https://github.com/jasonjgardner/blockbench-plugins/", + "variant": "both", + "version": "1.0.0", + "tags": [ + "Codec", + "PBR" + ], + "min_version": "4.12.1" } } From 57c50c78279861f49e0146a4fb455c8fab5a7e63 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 18 Jan 2025 12:55:07 -0600 Subject: [PATCH 5/5] fix: Revert PBR plugin USDZ export --- src/pbr_preview/src/lib/io/UsdzExporter.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pbr_preview/src/lib/io/UsdzExporter.ts b/src/pbr_preview/src/lib/io/UsdzExporter.ts index ed8a97ce..d7fd6b68 100644 --- a/src/pbr_preview/src/lib/io/UsdzExporter.ts +++ b/src/pbr_preview/src/lib/io/UsdzExporter.ts @@ -1,7 +1,8 @@ +import { three as THREE, jszip as JSZip } from "../../deps"; + class USDZExporter { async parse(scene: THREE.Scene): Promise { - // @ts-expect-error JSZip is on window - const zip = new window.JSZip(); + const zip = new JSZip(); const modelFileName = "model.usda"; // model file should be first in USDZ archive so we init it here zip.file(modelFileName, ""); @@ -44,7 +45,7 @@ class USDZExporter { for (const id in textures) { const texture = textures[id]; const color = id.split("_")[1]; - const isRGBA = texture.format === window.THREE.RGBAFormat; + const isRGBA = texture.format === THREE.RGBAFormat; const canvas = imageToCanvas(texture.image, color); const blob = await new Promise((resolve) => canvas.toBlob(