@ioai/robot_viewer 0.0.0-stage → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +217 -2
- package/bin/cli.js +83 -0
- package/dist/assets/css/PlotPanel-3e7d7555.css +1 -0
- package/dist/assets/js/ColladaLoader-2a0c4638.js +1 -0
- package/dist/assets/js/GLTFLoader-7a7f40d7.js +1 -0
- package/dist/assets/js/MTLLoader-b6ce88b7.js +1 -0
- package/dist/assets/js/OBJLoader-7667aa01.js +1 -0
- package/dist/assets/js/PlotPanel-09f986c7.js +1 -0
- package/dist/assets/js/STLLoader-de0915d4.js +1 -0
- package/dist/assets/js/URDFLoader-a34b6917.js +1 -0
- package/dist/assets/js/USDZLoader-64197f74.js +7 -0
- package/dist/assets/js/__vite-browser-external-afb87ea8.js +1 -0
- package/dist/assets/js/codemirror-c3cec7b3.js +1 -0
- package/dist/assets/js/codemirror-themes-e6995a6a.js +1 -0
- package/dist/assets/js/d3-2e308866.js +1 -0
- package/dist/assets/js/index-56e94ce5.js +6 -0
- package/dist/assets/js/mujoco_wasm-345a2576.js +1 -0
- package/dist/assets/js/three-9c891de0.js +6 -0
- package/dist/camera-publish-worker.js +1 -0
- package/dist/favicon.svg +2 -0
- package/dist/heartbeat-worker.js +1 -0
- package/dist/index.html +4373 -0
- package/dist/models/mocap/blender_human.urdf +2319 -0
- package/dist/models/mocap/blender_human_skeleton_v4.urdf +1432 -0
- package/dist/models/mocap/blender_human_skeleton_v5.urdf +2717 -0
- package/dist/models/mocap/meshes/v4/L_base_link.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer1_1.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer1_2.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer1_3.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer1_4.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer1_5.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer1base.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer2_1.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer2_2.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer2_3.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer2_4.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer2_5.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer3_1.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer3_2.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer3_3.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer3_4.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer3_5.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer4_1.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer4_2.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer4_3.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer4_4.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer4_5.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer5_1.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer5_2.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer5_3.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer5_4.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_figer5_5.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_handle.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_handleseat.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_mark.STL +0 -0
- package/dist/models/mocap/meshes/v4/L_markseat.STL +0 -0
- package/dist/models/mocap/meshes/v4/Lgamepadbase.STL +0 -0
- package/dist/models/mocap/meshes/v4/Link1.STL +0 -0
- package/dist/models/mocap/meshes/v4/Link2.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_base_link.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer1_1.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer1_2.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer1_3.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer1_4.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer1_5.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer1base.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer2_1.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer2_2.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer2_3.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer2_4.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer2_5.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer3_1.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer3_2.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer3_3.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer3_4.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer3_5.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer4_1.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer4_2.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer4_3.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer4_4.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer4_5.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer5_1.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer5_2.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer5_3.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer5_4.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_figer5_5.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_handle.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_handleseat.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_mark.STL +0 -0
- package/dist/models/mocap/meshes/v4/R_markseat.STL +0 -0
- package/dist/models/mocap/meshes/v4/Rgamepadbase.STL +0 -0
- package/dist/models/mocap/meshes/v4/base_link.STL +0 -0
- package/dist/models/mocap/meshes/v4/r_figer1_1.STL +0 -0
- package/dist/models/mocap/meshes/v5/L_handleseat.STL +0 -0
- package/dist/models/mocap/meshes/v5/R_handleseat.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonBase.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonIndex1.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonIndex2.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonIndex3.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonIndex4.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonMiddle1.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonMiddle2.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonMiddle3.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonMiddle4.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonPinky1.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonPinky2.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonPinky3.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonPinky4.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonRing1.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonRing2.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonRing3.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonRing4.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonThumb1.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonThumb2.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonThumb3.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonThumb4.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_LeftSkeletonThumbBase.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonBase.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonIndex1.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonIndex2.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonIndex3.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonIndex4.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonMiddle1.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonMiddle2.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonMiddle3.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonMiddle4.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonPinky1.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonPinky2.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonPinky3.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonPinky4.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonRing1.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonRing2.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonRing3.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonRing4.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonThumb1.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonThumb2.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonThumb3.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonThumb4.STL +0 -0
- package/dist/models/mocap/meshes/v5/link_RightSkeletonThumbBase.STL +0 -0
- package/dist/scripts/start-rosbridge.sh +77 -0
- package/dist/usd-iframe.html +41 -0
- package/dist/usd-viewer/bindings/.gitattributes +2 -0
- package/dist/usd-viewer/bindings/emHdBindings.data +19331 -0
- package/dist/usd-viewer/bindings/emHdBindings.js +12281 -0
- package/dist/usd-viewer/bindings/emHdBindings.wasm +0 -0
- package/dist/usd-viewer/bindings/emHdBindings.worker.js +124 -0
- package/dist/usd-viewer/environments/neutral.hdr +0 -0
- package/dist/usd-viewer/error-handler.js +57 -0
- package/dist/usd-viewer/hydra/HydraPrimitives.js +740 -0
- package/dist/usd-viewer/hydra/TextureRegistry.js +130 -0
- package/dist/usd-viewer/hydra/ThreeJsRenderDelegate.js +65 -0
- package/dist/usd-viewer/index.d.ts +1 -0
- package/dist/usd-viewer/main.js +1018 -0
- package/dist/usd-viewer/usd.html +36 -0
- package/dist/usd-viewer/usdUtils.js +67 -0
- package/package.json +70 -4
- package/server/package.json +14 -0
- package/server/static-server.js +106 -0
|
@@ -0,0 +1,1018 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Vector3,
|
|
3
|
+
Box3,
|
|
4
|
+
PerspectiveCamera,
|
|
5
|
+
Scene,
|
|
6
|
+
Group,
|
|
7
|
+
WebGLRenderer,
|
|
8
|
+
SRGBColorSpace,
|
|
9
|
+
NeutralToneMapping,
|
|
10
|
+
VSMShadowMap,
|
|
11
|
+
PMREMGenerator,
|
|
12
|
+
EquirectangularReflectionMapping,
|
|
13
|
+
} from "three";
|
|
14
|
+
import { ThreeRenderDelegateInterface } from "./hydra/ThreeJsRenderDelegate.js";
|
|
15
|
+
import {
|
|
16
|
+
shouldInclude,
|
|
17
|
+
isUsdFile,
|
|
18
|
+
getFileFromHandle,
|
|
19
|
+
parseFilePath,
|
|
20
|
+
safeCall,
|
|
21
|
+
getEntryFile,
|
|
22
|
+
} from "./usdUtils.js";
|
|
23
|
+
import { RGBELoader } from "three/addons/loaders/RGBELoader.js";
|
|
24
|
+
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
|
25
|
+
import { GLTFExporter } from "three/addons/exporters/GLTFExporter.js";
|
|
26
|
+
import "./bindings/emHdBindings.js";
|
|
27
|
+
|
|
28
|
+
const getUsdModule = globalThis["NEEDLE:USD:GET"];
|
|
29
|
+
|
|
30
|
+
const SKIP_DIRS = [
|
|
31
|
+
".git",
|
|
32
|
+
"node_modules",
|
|
33
|
+
"__pycache__",
|
|
34
|
+
".vscode",
|
|
35
|
+
".idea",
|
|
36
|
+
"build",
|
|
37
|
+
"dist",
|
|
38
|
+
".next",
|
|
39
|
+
];
|
|
40
|
+
const SKIP_FILES = [
|
|
41
|
+
".gitignore",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE",
|
|
44
|
+
"package.json",
|
|
45
|
+
".DS_Store",
|
|
46
|
+
".env",
|
|
47
|
+
];
|
|
48
|
+
const SYS_DIRS = ["/dev/", "/proc/", "/home/", "/tmp/", "/usd/"];
|
|
49
|
+
|
|
50
|
+
export function init(options = { container: null, hdrPath: null }) {
|
|
51
|
+
return new Promise((resolveInit) => {
|
|
52
|
+
if (!options?.container) {
|
|
53
|
+
throw new Error("init: options.container is required");
|
|
54
|
+
}
|
|
55
|
+
options.hdrPath ||= "/usd-viewer/environments/neutral.hdr";
|
|
56
|
+
|
|
57
|
+
let handle = null;
|
|
58
|
+
|
|
59
|
+
const run = () => {
|
|
60
|
+
let USD;
|
|
61
|
+
let resolveUsdReady;
|
|
62
|
+
const usdReady = new Promise((resolve) => {
|
|
63
|
+
resolveUsdReady = resolve;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Install a lightweight fetch rewrite so requests to "/host/..." are
|
|
67
|
+
// mapped to the current asset base directory of the last loaded URL
|
|
68
|
+
function installFetchRewrite() {
|
|
69
|
+
if (window.__usdFetchRewritten) return;
|
|
70
|
+
const origFetch = window.fetch.bind(window);
|
|
71
|
+
window.fetch = (input, init) => {
|
|
72
|
+
try {
|
|
73
|
+
const url = typeof input === "string" ? input : input?.url;
|
|
74
|
+
if (url?.startsWith("/host/") && window.__usdAssetBase) {
|
|
75
|
+
const mapped = window.__usdAssetBase + url.substring(6);
|
|
76
|
+
return origFetch(mapped, init);
|
|
77
|
+
}
|
|
78
|
+
} catch {}
|
|
79
|
+
return origFetch(input, init);
|
|
80
|
+
};
|
|
81
|
+
window.__usdFetchRewritten = true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let currentDisplayFilename = "";
|
|
85
|
+
const initPromise = setup();
|
|
86
|
+
|
|
87
|
+
console.log("Loading USD Module...");
|
|
88
|
+
try {
|
|
89
|
+
Promise.all([
|
|
90
|
+
getUsdModule({
|
|
91
|
+
mainScriptUrlOrBlob: "/usd-viewer/bindings/emHdBindings.js",
|
|
92
|
+
locateFile: (file) => {
|
|
93
|
+
return "/usd-viewer/bindings/" + file;
|
|
94
|
+
},
|
|
95
|
+
// Suppress noisy OpenUSD discovery warnings that don't affect functionality
|
|
96
|
+
printErr: (text) => {
|
|
97
|
+
try {
|
|
98
|
+
const s = String(text || "");
|
|
99
|
+
if (
|
|
100
|
+
s.includes("_FindAndInstantiateDiscoveryPlugins") ||
|
|
101
|
+
s.includes("/ndr/registry.cpp") ||
|
|
102
|
+
s.includes("Failed verification: ' pluginFactory '") ||
|
|
103
|
+
// Harmless when loading packaged USDZ read-only; USD attempts to save are blocked
|
|
104
|
+
s.includes("_WriteToFile") ||
|
|
105
|
+
s.includes("/sdf/layer.cpp") ||
|
|
106
|
+
s.includes(
|
|
107
|
+
"writing package usdz layer is not allowed through this API"
|
|
108
|
+
)
|
|
109
|
+
) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
} catch {}
|
|
113
|
+
// Fallback to standard error output for everything else
|
|
114
|
+
try {
|
|
115
|
+
console.error(text);
|
|
116
|
+
} catch {}
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
initPromise,
|
|
120
|
+
]).then(async ([Usd]) => {
|
|
121
|
+
USD = Usd;
|
|
122
|
+
if (resolveUsdReady) resolveUsdReady(USD);
|
|
123
|
+
animate();
|
|
124
|
+
});
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (error.toString().indexOf("SharedArrayBuffer") >= 0) {
|
|
127
|
+
console.log(
|
|
128
|
+
error,
|
|
129
|
+
"Your current browser doesn't support SharedArrayBuffer which is required for USD."
|
|
130
|
+
);
|
|
131
|
+
} else {
|
|
132
|
+
console.log(
|
|
133
|
+
"Your current browser doesn't support USD-for-web. Error during initialization: " +
|
|
134
|
+
error
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let timeout = 40;
|
|
140
|
+
let endTimeCode = 1;
|
|
141
|
+
let ready = false;
|
|
142
|
+
|
|
143
|
+
const usdzExportBtn = document.getElementById("export-usdz");
|
|
144
|
+
if (usdzExportBtn)
|
|
145
|
+
usdzExportBtn.addEventListener("click", () => {
|
|
146
|
+
alert("usdz");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const gltfExportBtn = document.getElementById("export-gltf");
|
|
150
|
+
if (gltfExportBtn)
|
|
151
|
+
gltfExportBtn.addEventListener("click", (evt) => {
|
|
152
|
+
const exporter = new GLTFExporter();
|
|
153
|
+
console.log("EXPORTING GLTF", window.usdRoot);
|
|
154
|
+
exporter.parse(
|
|
155
|
+
window.usdRoot,
|
|
156
|
+
function (gltf) {
|
|
157
|
+
const blob = new Blob([gltf], {
|
|
158
|
+
type: "application/octet-stream",
|
|
159
|
+
});
|
|
160
|
+
const url = URL.createObjectURL(blob);
|
|
161
|
+
const a = document.createElement("a");
|
|
162
|
+
a.href = url;
|
|
163
|
+
let filename = currentDisplayFilename;
|
|
164
|
+
// strip extension, strip path
|
|
165
|
+
filename =
|
|
166
|
+
filename.split("/").pop()?.split(".")[0].split("?")[0] ||
|
|
167
|
+
"export";
|
|
168
|
+
a.download = filename + ".glb";
|
|
169
|
+
a.click();
|
|
170
|
+
URL.revokeObjectURL(url);
|
|
171
|
+
},
|
|
172
|
+
function (error) {
|
|
173
|
+
console.error(error);
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
binary: true,
|
|
177
|
+
// not possible right now since USD controls animation bindings,
|
|
178
|
+
// it's not a three.js clip
|
|
179
|
+
animations: [
|
|
180
|
+
// window.usdRoot.animations[0]
|
|
181
|
+
],
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
evt.preventDefault();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
function getAllLoadedFiles() {
|
|
188
|
+
const filePaths = [];
|
|
189
|
+
|
|
190
|
+
getAllLoadedFilePaths("/", filePaths);
|
|
191
|
+
|
|
192
|
+
return filePaths;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function getAllLoadedFilePaths(currentPath, paths) {
|
|
196
|
+
const files = USD.FS_readdir(currentPath);
|
|
197
|
+
for (const file of files) {
|
|
198
|
+
if (file === "." || file === "..") continue;
|
|
199
|
+
const newPath = currentPath + file + "/";
|
|
200
|
+
const data = USD.FS_analyzePath(currentPath + file + "/");
|
|
201
|
+
if (data.object.node_ops.readdir) {
|
|
202
|
+
if (!SYS_DIRS.includes(newPath))
|
|
203
|
+
getAllLoadedFilePaths(newPath, paths);
|
|
204
|
+
} else {
|
|
205
|
+
paths.push(data.path);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// safeCall imported
|
|
211
|
+
|
|
212
|
+
function clearStage() {
|
|
213
|
+
if (!USD) {
|
|
214
|
+
console.warn("USD not ready; skipping clearStage.");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// Try to dispose the driver/stage first to avoid any layer save attempts
|
|
218
|
+
if (!safeCall(window.driver, "Dispose")) {
|
|
219
|
+
safeCall(window.driver, "Destroy");
|
|
220
|
+
}
|
|
221
|
+
// Clear the rendered scene graph before touching the virtual FS
|
|
222
|
+
safeCall(window.usdRoot, "clear");
|
|
223
|
+
|
|
224
|
+
// Then unlink files from the in-memory FS, but keep .usdz packages to
|
|
225
|
+
// avoid triggering writes to packaged layers
|
|
226
|
+
const allFilePaths = getAllLoadedFiles();
|
|
227
|
+
for (const file of allFilePaths) {
|
|
228
|
+
if (String(file).toLowerCase().endsWith(".usdz")) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
USD.FS_unlink(file, true);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function addPath(root, path) {
|
|
236
|
+
const files = USD.FS_readdir(path);
|
|
237
|
+
for (const file of files) {
|
|
238
|
+
if (file === "." || file === "..") continue;
|
|
239
|
+
const newPath = path + file + "/";
|
|
240
|
+
const data = USD.FS_analyzePath(path + file + "/");
|
|
241
|
+
if (data.object.node_ops.readdir) {
|
|
242
|
+
if (!SYS_DIRS.includes(newPath)) {
|
|
243
|
+
root[file] = {};
|
|
244
|
+
addPath(root[file], newPath);
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
root[file] = data;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function loadUsdFile(directory, filename, path, isRootFile = true) {
|
|
253
|
+
currentDisplayFilename = filename;
|
|
254
|
+
ready = false;
|
|
255
|
+
|
|
256
|
+
// should be loaded last
|
|
257
|
+
if (!isRootFile) return;
|
|
258
|
+
|
|
259
|
+
let driver = null;
|
|
260
|
+
const delegateConfig = {
|
|
261
|
+
usdRoot: window.usdRoot,
|
|
262
|
+
paths: [],
|
|
263
|
+
driver: () => driver,
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const renderInterface = (window.renderInterface =
|
|
267
|
+
new ThreeRenderDelegateInterface(delegateConfig));
|
|
268
|
+
driver = new USD.HdWebSyncDriver(renderInterface, path);
|
|
269
|
+
if (driver instanceof Promise) {
|
|
270
|
+
driver = await driver;
|
|
271
|
+
}
|
|
272
|
+
window.driver = driver;
|
|
273
|
+
window.driver.Draw();
|
|
274
|
+
|
|
275
|
+
let stage = window.driver.GetStage();
|
|
276
|
+
if (stage instanceof Promise) {
|
|
277
|
+
stage = await stage;
|
|
278
|
+
stage = window.driver.GetStage();
|
|
279
|
+
}
|
|
280
|
+
window.usdStage = stage;
|
|
281
|
+
if (stage.GetEndTimeCode) {
|
|
282
|
+
endTimeCode = stage.GetEndTimeCode();
|
|
283
|
+
timeout = 1000 / stage.GetTimeCodesPerSecond();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// if up axis is z, rotate, otherwise make sure rotation is 0, in case we rotated in the past and need to undo it
|
|
287
|
+
window.usdRoot.rotation.x =
|
|
288
|
+
String.fromCharCode(stage.GetUpAxis()) === "z" ? -Math.PI / 2 : 0;
|
|
289
|
+
|
|
290
|
+
fitCameraToSelection(window.camera, window._controls, [window.usdRoot]);
|
|
291
|
+
ready = true;
|
|
292
|
+
|
|
293
|
+
console.log('[USD Viewer] Loading complete! Scene state:');
|
|
294
|
+
console.log(' - usdRoot.children:', window.usdRoot.children.length);
|
|
295
|
+
console.log(' - camera.position:', window.camera.position);
|
|
296
|
+
console.log(' - renderer.domElement size:', {
|
|
297
|
+
width: window.renderer.domElement.width,
|
|
298
|
+
height: window.renderer.domElement.height,
|
|
299
|
+
clientWidth: window.renderer.domElement.clientWidth,
|
|
300
|
+
clientHeight: window.renderer.domElement.clientHeight
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const root = {};
|
|
304
|
+
addPath(root, "/");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// from https://discourse.threejs.org/t/camera-zoom-to-fit-object/936/24
|
|
308
|
+
function fitCameraToSelection(
|
|
309
|
+
camera,
|
|
310
|
+
controls,
|
|
311
|
+
selection,
|
|
312
|
+
fitOffset = 1.5
|
|
313
|
+
) {
|
|
314
|
+
const size = new Vector3();
|
|
315
|
+
const center = new Vector3();
|
|
316
|
+
const box = new Box3();
|
|
317
|
+
|
|
318
|
+
box.makeEmpty();
|
|
319
|
+
for (const object of selection) {
|
|
320
|
+
box.expandByObject(object);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
box.getSize(size);
|
|
324
|
+
box.getCenter(center);
|
|
325
|
+
|
|
326
|
+
if (
|
|
327
|
+
Number.isNaN(size.x) ||
|
|
328
|
+
Number.isNaN(size.y) ||
|
|
329
|
+
Number.isNaN(size.z) ||
|
|
330
|
+
Number.isNaN(center.x) ||
|
|
331
|
+
Number.isNaN(center.y) ||
|
|
332
|
+
Number.isNaN(center.z)
|
|
333
|
+
) {
|
|
334
|
+
console.warn(
|
|
335
|
+
"Fit Camera failed: NaN values found, some objects may not have any mesh data.",
|
|
336
|
+
selection,
|
|
337
|
+
size
|
|
338
|
+
);
|
|
339
|
+
if (controls) controls.update();
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (!controls) {
|
|
344
|
+
console.warn(
|
|
345
|
+
"No camera controls object found, something went wrong."
|
|
346
|
+
);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const maxSize = Math.max(size.x, size.y, size.z);
|
|
351
|
+
const fitHeightDistance =
|
|
352
|
+
maxSize / (2 * Math.atan((Math.PI * camera.fov) / 360));
|
|
353
|
+
const fitWidthDistance = fitHeightDistance / camera.aspect;
|
|
354
|
+
const distance =
|
|
355
|
+
fitOffset * Math.max(fitHeightDistance, fitWidthDistance);
|
|
356
|
+
|
|
357
|
+
if (distance == 0) {
|
|
358
|
+
console.warn(
|
|
359
|
+
"Fit Camera failed: distance is 0, some objects may not have any mesh data."
|
|
360
|
+
);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
camera.position.z = 7;
|
|
365
|
+
camera.position.y = 7;
|
|
366
|
+
camera.position.x = 0;
|
|
367
|
+
|
|
368
|
+
const direction = controls.target
|
|
369
|
+
.clone()
|
|
370
|
+
.sub(camera.position)
|
|
371
|
+
.normalize()
|
|
372
|
+
.multiplyScalar(distance);
|
|
373
|
+
|
|
374
|
+
controls.maxDistance = distance * 10;
|
|
375
|
+
controls.target.copy(center);
|
|
376
|
+
|
|
377
|
+
camera.near = distance / 100;
|
|
378
|
+
camera.far = distance * 100;
|
|
379
|
+
|
|
380
|
+
camera.updateProjectionMatrix();
|
|
381
|
+
|
|
382
|
+
camera.position.copy(controls.target).sub(direction);
|
|
383
|
+
controls.update();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async function setup() {
|
|
387
|
+
// Use container size instead of window size
|
|
388
|
+
const width = options.container.clientWidth || window.innerWidth || 800;
|
|
389
|
+
const height = options.container.clientHeight || window.innerHeight || 600;
|
|
390
|
+
const aspect = width / height;
|
|
391
|
+
console.log('[USD Viewer] Setup - container size:', { width, height, aspect });
|
|
392
|
+
|
|
393
|
+
const camera = (window.camera = new PerspectiveCamera(
|
|
394
|
+
27,
|
|
395
|
+
aspect,
|
|
396
|
+
1,
|
|
397
|
+
3500
|
|
398
|
+
));
|
|
399
|
+
camera.position.z = 7;
|
|
400
|
+
camera.position.y = 7;
|
|
401
|
+
camera.position.x = 0;
|
|
402
|
+
|
|
403
|
+
const scene = (window.scene = new Scene());
|
|
404
|
+
|
|
405
|
+
const usdRoot = (window.usdRoot = new Group());
|
|
406
|
+
usdRoot.name = "USD Root";
|
|
407
|
+
scene.add(usdRoot);
|
|
408
|
+
|
|
409
|
+
const renderer = (window.renderer = new WebGLRenderer({
|
|
410
|
+
antialias: true,
|
|
411
|
+
alpha: true,
|
|
412
|
+
}));
|
|
413
|
+
renderer.setPixelRatio(window.devicePixelRatio);
|
|
414
|
+
|
|
415
|
+
// Use previously declared width and height
|
|
416
|
+
console.log('[USD Viewer] Set renderer size:', { width, height });
|
|
417
|
+
renderer.setSize(width, height);
|
|
418
|
+
renderer.outputColorSpace = SRGBColorSpace;
|
|
419
|
+
renderer.toneMapping = NeutralToneMapping;
|
|
420
|
+
renderer.shadowMap.enabled = false;
|
|
421
|
+
renderer.shadowMap.type = VSMShadowMap;
|
|
422
|
+
// Use transparent background, inherit parent page style
|
|
423
|
+
renderer.setClearColor(0x000000, 0);
|
|
424
|
+
|
|
425
|
+
console.log('[USD Viewer] Renderer created:', {
|
|
426
|
+
domElement: renderer.domElement,
|
|
427
|
+
width: renderer.domElement.width,
|
|
428
|
+
height: renderer.domElement.height,
|
|
429
|
+
parent: renderer.domElement.parentElement
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const envMapPromise = new Promise((resolve) => {
|
|
433
|
+
const pmremGenerator = new PMREMGenerator(renderer);
|
|
434
|
+
pmremGenerator.compileCubemapShader();
|
|
435
|
+
|
|
436
|
+
new RGBELoader().load(
|
|
437
|
+
options.hdrPath,
|
|
438
|
+
(texture) => {
|
|
439
|
+
const hdrRenderTarget =
|
|
440
|
+
pmremGenerator.fromEquirectangular(texture);
|
|
441
|
+
|
|
442
|
+
texture.mapping = EquirectangularReflectionMapping;
|
|
443
|
+
texture.needsUpdate = true;
|
|
444
|
+
scene.environment = hdrRenderTarget.texture;
|
|
445
|
+
resolve();
|
|
446
|
+
},
|
|
447
|
+
undefined,
|
|
448
|
+
(err) => {
|
|
449
|
+
console.error(
|
|
450
|
+
"An error occurred loading the HDR environment map.",
|
|
451
|
+
err
|
|
452
|
+
);
|
|
453
|
+
resolve();
|
|
454
|
+
}
|
|
455
|
+
);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
console.log('[USD Viewer] Add renderer canvas to container:', options.container);
|
|
459
|
+
options.container.appendChild(renderer.domElement);
|
|
460
|
+
console.log('[USD Viewer] Canvas added, canvas element:', renderer.domElement);
|
|
461
|
+
const controls = (window._controls = new OrbitControls(
|
|
462
|
+
camera,
|
|
463
|
+
renderer.domElement
|
|
464
|
+
));
|
|
465
|
+
controls.enableDamping = true;
|
|
466
|
+
controls.dampingFactor = 0.2;
|
|
467
|
+
controls.update();
|
|
468
|
+
|
|
469
|
+
window.addEventListener("resize", onWindowResize);
|
|
470
|
+
|
|
471
|
+
render();
|
|
472
|
+
return envMapPromise;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Optional: pause helper removed to avoid global DOM coupling
|
|
476
|
+
|
|
477
|
+
async function animate() {
|
|
478
|
+
window._controls.update();
|
|
479
|
+
let secs = new Date().getTime() / 1000;
|
|
480
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
481
|
+
const time = (secs * (1000 / timeout)) % endTimeCode;
|
|
482
|
+
if (
|
|
483
|
+
window.driver &&
|
|
484
|
+
window.driver.SetTime &&
|
|
485
|
+
window.driver.Draw &&
|
|
486
|
+
ready
|
|
487
|
+
) {
|
|
488
|
+
window.driver.SetTime(time);
|
|
489
|
+
window.driver.Draw();
|
|
490
|
+
render();
|
|
491
|
+
}
|
|
492
|
+
requestAnimationFrame(animate.bind(null, timeout, endTimeCode));
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function onWindowResize() {
|
|
496
|
+
const width = options.container?.clientWidth || window.innerWidth;
|
|
497
|
+
const height = options.container?.clientHeight || window.innerHeight;
|
|
498
|
+
camera.aspect = width / height;
|
|
499
|
+
camera.updateProjectionMatrix();
|
|
500
|
+
renderer.setSize(width, height);
|
|
501
|
+
console.log('[USD Viewer] Resize:', { width, height });
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function render() {
|
|
505
|
+
// const time = Date.now() * 0.001;
|
|
506
|
+
if (window.renderer.render && window.scene) {
|
|
507
|
+
window.renderer.render(window.scene, window.camera);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// getFileFromHandle imported
|
|
512
|
+
|
|
513
|
+
// parseFilePath imported
|
|
514
|
+
|
|
515
|
+
async function loadFile(
|
|
516
|
+
fileOrHandle,
|
|
517
|
+
isRootFile = true,
|
|
518
|
+
fullPath = undefined
|
|
519
|
+
) {
|
|
520
|
+
try {
|
|
521
|
+
const file = await getFileFromHandle(fileOrHandle);
|
|
522
|
+
const { fileName, directory } = parseFilePath(fullPath, file.name);
|
|
523
|
+
|
|
524
|
+
const reader = new FileReader();
|
|
525
|
+
const loadingPromise = new Promise((resolve, reject) => {
|
|
526
|
+
reader.onloadend = resolve;
|
|
527
|
+
reader.onerror = reject;
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
reader.onload = async function (event) {
|
|
531
|
+
// Ensure USD module is initialized before filesystem operations
|
|
532
|
+
if (!USD) await usdReady;
|
|
533
|
+
|
|
534
|
+
USD.FS_createPath("", directory, true, true);
|
|
535
|
+
// Mount file as read-only to prevent USD from attempting write-backs to packages
|
|
536
|
+
USD.FS_createDataFile(
|
|
537
|
+
directory,
|
|
538
|
+
fileName,
|
|
539
|
+
new Uint8Array(event.target.result),
|
|
540
|
+
true /* canRead */,
|
|
541
|
+
false /* canWrite */,
|
|
542
|
+
true /* canOwn */
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
loadUsdFile(directory, fileName, fullPath, isRootFile);
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
reader.readAsArrayBuffer(file);
|
|
549
|
+
await loadingPromise;
|
|
550
|
+
} catch (ex) {
|
|
551
|
+
console.warn("Error loading file", fileOrHandle, ex);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// isUsdFile imported
|
|
556
|
+
|
|
557
|
+
function testAndLoadFile(file) {
|
|
558
|
+
if (isUsdFile(file.name)) {
|
|
559
|
+
clearStage();
|
|
560
|
+
loadFile(file);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* @param {FileSystemDirectoryEntry} directory
|
|
566
|
+
*/
|
|
567
|
+
async function readDirectory(directory) {
|
|
568
|
+
let entries = [];
|
|
569
|
+
|
|
570
|
+
let getAllDirectoryEntries = async (dirReader) => {
|
|
571
|
+
let entries = [];
|
|
572
|
+
let readEntries = async () => {
|
|
573
|
+
let result = await new Promise((resolve, reject) =>
|
|
574
|
+
dirReader.readEntries(resolve, reject)
|
|
575
|
+
);
|
|
576
|
+
if (result.length === 0) return entries;
|
|
577
|
+
else return entries.concat(result, await readEntries());
|
|
578
|
+
};
|
|
579
|
+
return await readEntries();
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* @param {FileSystemDirectoryReader} dirReader
|
|
584
|
+
* @param {FileSystemDirectoryEntry} directory
|
|
585
|
+
* @returns {Promise<number>}
|
|
586
|
+
*/
|
|
587
|
+
let getEntries = async (directory) => {
|
|
588
|
+
let dirReader = directory.createReader();
|
|
589
|
+
await new Promise(async (resolve) => {
|
|
590
|
+
// Call the reader.readEntries() until no more results are returned.
|
|
591
|
+
|
|
592
|
+
const results = await getAllDirectoryEntries(dirReader);
|
|
593
|
+
|
|
594
|
+
if (results.length) {
|
|
595
|
+
// entries = entries.concat(results);
|
|
596
|
+
for (let entry of results) {
|
|
597
|
+
if (entry.isDirectory) {
|
|
598
|
+
const foundFiles = await getEntries(entry);
|
|
599
|
+
if (foundFiles === 100)
|
|
600
|
+
console.warn(
|
|
601
|
+
"Found more than 100 files in directory",
|
|
602
|
+
entry
|
|
603
|
+
);
|
|
604
|
+
} else {
|
|
605
|
+
entries.push(entry);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
resolve(results.length);
|
|
610
|
+
});
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
await getEntries(directory);
|
|
614
|
+
return entries;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* @param {FileSystemEntry[]} entries
|
|
619
|
+
*/
|
|
620
|
+
async function handleFilesystemEntries(entries) {
|
|
621
|
+
const allFiles = [];
|
|
622
|
+
|
|
623
|
+
for (let entry of entries) {
|
|
624
|
+
if (entry.isFile) {
|
|
625
|
+
if (shouldInclude(entry.name)) allFiles.push(entry);
|
|
626
|
+
} else if (entry.isDirectory) {
|
|
627
|
+
if (SKIP_DIRS.includes(entry.name)) continue;
|
|
628
|
+
const files = await readDirectory(entry);
|
|
629
|
+
allFiles.push(...files.filter((file) => shouldInclude(file.name)));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// clear current set of files
|
|
634
|
+
clearStage();
|
|
635
|
+
|
|
636
|
+
// Find root file candidates
|
|
637
|
+
const usdFiles = allFiles.filter((file) => isUsdFile(file.name));
|
|
638
|
+
const usdaFiles = usdFiles.filter((file) =>
|
|
639
|
+
file.name.endsWith(".usda")
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
// Prefer .usda files, otherwise use first USD file
|
|
643
|
+
let rootFile = usdaFiles[0] || usdFiles[0];
|
|
644
|
+
|
|
645
|
+
if (rootFile) {
|
|
646
|
+
allFiles.splice(allFiles.indexOf(rootFile), 1);
|
|
647
|
+
} else {
|
|
648
|
+
console.warn("No USD file found");
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const getFile = getEntryFile;
|
|
653
|
+
|
|
654
|
+
// Mount all non-root files concurrently; order doesn't matter as we load the root last
|
|
655
|
+
|
|
656
|
+
// Load all non-root files into memory
|
|
657
|
+
const loadPromises = allFiles.map(async (file) => {
|
|
658
|
+
const fileObj = await getFile(file);
|
|
659
|
+
if (fileObj) await loadFile(fileObj, false, file.fullPath);
|
|
660
|
+
});
|
|
661
|
+
await Promise.all(loadPromises);
|
|
662
|
+
|
|
663
|
+
// Load the root file last
|
|
664
|
+
if (rootFile) {
|
|
665
|
+
if (!isUsdFile(rootFile.name)) {
|
|
666
|
+
console.error("Not a supported file format:", rootFile.name);
|
|
667
|
+
} else {
|
|
668
|
+
const rootFileObj = await getFile(rootFile);
|
|
669
|
+
if (rootFileObj) loadFile(rootFileObj, true, rootFile.fullPath);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* @param {DataTransfer} dataTransfer
|
|
676
|
+
*/
|
|
677
|
+
function processDataTransfer(dataTransfer) {
|
|
678
|
+
if (dataTransfer.items) {
|
|
679
|
+
/** @type {FileSystemEntry[]} */
|
|
680
|
+
const allEntries = [];
|
|
681
|
+
|
|
682
|
+
let haveGetAsEntry = false;
|
|
683
|
+
if (dataTransfer.items.length > 0)
|
|
684
|
+
haveGetAsEntry =
|
|
685
|
+
"getAsEntry" in dataTransfer.items[0] ||
|
|
686
|
+
"webkitGetAsEntry" in dataTransfer.items[0];
|
|
687
|
+
|
|
688
|
+
if (haveGetAsEntry) {
|
|
689
|
+
for (const item of dataTransfer.items) {
|
|
690
|
+
/** @type {FileSystemEntry} */
|
|
691
|
+
const entry =
|
|
692
|
+
"getAsEntry" in item
|
|
693
|
+
? item.getAsEntry()
|
|
694
|
+
: item.webkitGetAsEntry();
|
|
695
|
+
allEntries.push(entry);
|
|
696
|
+
}
|
|
697
|
+
handleFilesystemEntries(allEntries);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
for (const item of dataTransfer.items) {
|
|
702
|
+
// API when there's no "getAsEntry" support
|
|
703
|
+
console.log(item.kind, item);
|
|
704
|
+
if (item.kind === "file") {
|
|
705
|
+
var file = item.getAsFile();
|
|
706
|
+
testAndLoadFile(file);
|
|
707
|
+
}
|
|
708
|
+
// could also be a directory
|
|
709
|
+
else if (item.kind === "directory") {
|
|
710
|
+
var dirReader = item.createReader();
|
|
711
|
+
dirReader.readEntries(function (entries) {
|
|
712
|
+
for (var i = 0; i < entries.length; i++) {
|
|
713
|
+
console.log(entries[i].name);
|
|
714
|
+
var entry = entries[i];
|
|
715
|
+
if (entry.isFile) {
|
|
716
|
+
entry.file(function (file) {
|
|
717
|
+
testAndLoadFile(file);
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
} else {
|
|
725
|
+
for (const file of dataTransfer.files) {
|
|
726
|
+
testAndLoadFile(file);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Provide a minimal imperative API to the host (capturing the local scope)
|
|
732
|
+
handle = {
|
|
733
|
+
// Load a USD file from a URL
|
|
734
|
+
loadFromURL: async (url) => {
|
|
735
|
+
try {
|
|
736
|
+
if (!USD) await usdReady;
|
|
737
|
+
clearStage();
|
|
738
|
+
const parts = url.split("/");
|
|
739
|
+
const fileNameOnly = parts[parts.length - 1];
|
|
740
|
+
// For packaged usdz, mount read-only to avoid write attempts
|
|
741
|
+
if (fileNameOnly.toLowerCase().endsWith(".usdz")) {
|
|
742
|
+
const res = await fetch(url, { cache: "no-store" });
|
|
743
|
+
if (!res.ok) throw new Error("Failed to fetch " + url);
|
|
744
|
+
const buffer = await res.arrayBuffer();
|
|
745
|
+
const mountDir = "/host/";
|
|
746
|
+
USD.FS_createPath("", mountDir, true, true);
|
|
747
|
+
// If a previous package exists at the same path, remove it now that the stage is cleared
|
|
748
|
+
try {
|
|
749
|
+
const existing = USD.FS_analyzePath(mountDir + fileNameOnly);
|
|
750
|
+
if (existing?.exists) {
|
|
751
|
+
USD.FS_unlink(mountDir + fileNameOnly);
|
|
752
|
+
}
|
|
753
|
+
} catch {}
|
|
754
|
+
USD.FS_createDataFile(
|
|
755
|
+
mountDir,
|
|
756
|
+
fileNameOnly,
|
|
757
|
+
new Uint8Array(buffer),
|
|
758
|
+
true /* canRead */,
|
|
759
|
+
false /* canWrite */,
|
|
760
|
+
true /* canOwn */
|
|
761
|
+
);
|
|
762
|
+
await loadUsdFile(
|
|
763
|
+
mountDir,
|
|
764
|
+
fileNameOnly,
|
|
765
|
+
mountDir + fileNameOnly,
|
|
766
|
+
true
|
|
767
|
+
);
|
|
768
|
+
} else {
|
|
769
|
+
// For usd/usda/usdc, keep URL so relative asset paths resolve via HTTP
|
|
770
|
+
try {
|
|
771
|
+
const base = new URL(url, window.location.origin);
|
|
772
|
+
// ensure base URL ends with '/'
|
|
773
|
+
const baseDir = base.href.substring(
|
|
774
|
+
0,
|
|
775
|
+
base.href.lastIndexOf("/") + 1
|
|
776
|
+
);
|
|
777
|
+
window.__usdAssetBase = baseDir;
|
|
778
|
+
installFetchRewrite();
|
|
779
|
+
} catch {}
|
|
780
|
+
await loadUsdFile(undefined, fileNameOnly, url, true);
|
|
781
|
+
}
|
|
782
|
+
} catch (e) {
|
|
783
|
+
console.warn("loadFromURL error", e);
|
|
784
|
+
}
|
|
785
|
+
},
|
|
786
|
+
// Load from array buffer entries mounted into the in-memory FS
|
|
787
|
+
loadFromEntries: async (entries, primaryPath) => {
|
|
788
|
+
try {
|
|
789
|
+
if (!USD) await usdReady;
|
|
790
|
+
clearStage();
|
|
791
|
+
// Mount all entries first (order doesn't matter since we load the root explicitly last)
|
|
792
|
+
const list = (entries || []).slice();
|
|
793
|
+
for (const { path, buffer } of list) {
|
|
794
|
+
const fileName = path.split("/").pop();
|
|
795
|
+
let dir = path.slice(0, path.length - (fileName?.length || 0));
|
|
796
|
+
// Ensure dir is at least "/", cannot be empty string
|
|
797
|
+
if (!dir || dir === "") {
|
|
798
|
+
dir = "/";
|
|
799
|
+
}
|
|
800
|
+
// Remove trailing slash (if present)
|
|
801
|
+
if (dir.length > 1 && dir.endsWith("/")) {
|
|
802
|
+
dir = dir.slice(0, -1);
|
|
803
|
+
}
|
|
804
|
+
console.log('[USD] Mount file:', { path, fileName, dir });
|
|
805
|
+
USD.FS_createPath("", dir, true, true);
|
|
806
|
+
USD.FS_createDataFile(
|
|
807
|
+
dir,
|
|
808
|
+
fileName,
|
|
809
|
+
new Uint8Array(buffer),
|
|
810
|
+
true /* canRead */,
|
|
811
|
+
false /* canWrite */,
|
|
812
|
+
true /* canOwn */
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
// Determine root
|
|
816
|
+
let root = primaryPath;
|
|
817
|
+
if (root) {
|
|
818
|
+
// primaryPath may only be filename, need to find full path in list
|
|
819
|
+
const foundEntry = list.find((e) => e.path.endsWith(root) || e.path === root);
|
|
820
|
+
if (foundEntry) {
|
|
821
|
+
root = foundEntry.path;
|
|
822
|
+
console.log('[USD] primaryPath matched to full path:', root);
|
|
823
|
+
} else {
|
|
824
|
+
console.warn('[USD] primaryPath not found:', primaryPath);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (!root) {
|
|
828
|
+
// Prefer .usda, else any USD
|
|
829
|
+
root =
|
|
830
|
+
list.find((e) => e.path.endsWith(".usda"))?.path ||
|
|
831
|
+
list.find((e) => isUsdFile(e.path))?.path;
|
|
832
|
+
console.log('[USD] Auto-detected root file:', root);
|
|
833
|
+
}
|
|
834
|
+
if (root) {
|
|
835
|
+
const fileNameOnly = root.split("/").pop();
|
|
836
|
+
let dir = root.slice(0, root.length - (fileNameOnly?.length || 0));
|
|
837
|
+
if (!dir || dir === "") {
|
|
838
|
+
dir = "/";
|
|
839
|
+
}
|
|
840
|
+
if (dir.length > 1 && dir.endsWith("/")) {
|
|
841
|
+
dir = dir.slice(0, -1);
|
|
842
|
+
}
|
|
843
|
+
console.log('[USD] Load root file:', { root, dir, fileNameOnly });
|
|
844
|
+
await loadUsdFile(dir, fileNameOnly, root, true);
|
|
845
|
+
}
|
|
846
|
+
} catch (e) {
|
|
847
|
+
console.warn("loadFromEntries error", e);
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
// Load from a DataTransfer (e.g., from a drag/drop event)
|
|
851
|
+
loadFromDataTransfer: async (dataTransfer) => {
|
|
852
|
+
try {
|
|
853
|
+
if (!USD) await usdReady;
|
|
854
|
+
processDataTransfer(dataTransfer);
|
|
855
|
+
} catch (e) {
|
|
856
|
+
console.warn("loadFromDataTransfer error", e);
|
|
857
|
+
}
|
|
858
|
+
},
|
|
859
|
+
// Load directly from a FileList or array of File
|
|
860
|
+
loadFromFiles: async (files) => {
|
|
861
|
+
try {
|
|
862
|
+
if (!USD) await usdReady;
|
|
863
|
+
clearStage();
|
|
864
|
+
const fileArray = Array.from(files);
|
|
865
|
+
for (const file of fileArray) testAndLoadFile(file);
|
|
866
|
+
} catch (e) {
|
|
867
|
+
console.warn("loadFromFiles error", e);
|
|
868
|
+
}
|
|
869
|
+
},
|
|
870
|
+
// Load from a map of virtual paths -> File, with an optional primary root file path
|
|
871
|
+
loadFromFilesMap: async (filesMap, primaryPath) => {
|
|
872
|
+
try {
|
|
873
|
+
if (!USD) await usdReady;
|
|
874
|
+
clearStage();
|
|
875
|
+
const entries = Object.entries(filesMap).filter(([p]) => {
|
|
876
|
+
const name = p.split("/").pop() || p;
|
|
877
|
+
return (
|
|
878
|
+
!SKIP_FILES.includes(name) &&
|
|
879
|
+
!name.startsWith("._") &&
|
|
880
|
+
shouldInclude(name)
|
|
881
|
+
);
|
|
882
|
+
});
|
|
883
|
+
// Load all non-root files first
|
|
884
|
+
for (const [fullPath, file] of entries) {
|
|
885
|
+
if (primaryPath && fullPath === primaryPath) continue;
|
|
886
|
+
await loadFile(file, false, fullPath);
|
|
887
|
+
}
|
|
888
|
+
// Then load the primary/root if provided, else try to detect
|
|
889
|
+
if (primaryPath && filesMap[primaryPath]) {
|
|
890
|
+
await loadFile(filesMap[primaryPath], true, primaryPath);
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
// Detect a reasonable root (prefer .usda)
|
|
894
|
+
const usdaRoot = entries.find(([p]) => p.endsWith(".usda"));
|
|
895
|
+
const anyUsdRoot = entries.find(([p]) => isUsdFile(p));
|
|
896
|
+
const root = usdaRoot || anyUsdRoot;
|
|
897
|
+
if (root) {
|
|
898
|
+
await loadFile(root[1], true, root[0]);
|
|
899
|
+
}
|
|
900
|
+
} catch (e) {
|
|
901
|
+
console.warn("loadFromFilesMap error", e);
|
|
902
|
+
}
|
|
903
|
+
},
|
|
904
|
+
// Clear the current stage
|
|
905
|
+
clear: () => {
|
|
906
|
+
try {
|
|
907
|
+
clearStage();
|
|
908
|
+
} catch (e) {
|
|
909
|
+
console.warn("clear error", e);
|
|
910
|
+
}
|
|
911
|
+
},
|
|
912
|
+
// Dispose the viewer and remove listeners/canvas
|
|
913
|
+
dispose: () => {
|
|
914
|
+
try {
|
|
915
|
+
window.removeEventListener("resize", onWindowResize);
|
|
916
|
+
if (window.renderer && window.renderer.domElement) {
|
|
917
|
+
if (options.container.contains(window.renderer.domElement)) {
|
|
918
|
+
options.container.removeChild(window.renderer.domElement);
|
|
919
|
+
}
|
|
920
|
+
if (window.renderer.dispose) window.renderer.dispose();
|
|
921
|
+
}
|
|
922
|
+
} catch (e) {
|
|
923
|
+
console.warn("dispose error", e);
|
|
924
|
+
}
|
|
925
|
+
},
|
|
926
|
+
};
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
if (document.readyState === "loading") {
|
|
930
|
+
document.addEventListener(
|
|
931
|
+
"DOMContentLoaded",
|
|
932
|
+
() => {
|
|
933
|
+
run();
|
|
934
|
+
try {
|
|
935
|
+
if (resolveInit) resolveInit(handle);
|
|
936
|
+
} catch {}
|
|
937
|
+
},
|
|
938
|
+
{ once: true }
|
|
939
|
+
);
|
|
940
|
+
} else {
|
|
941
|
+
run();
|
|
942
|
+
try {
|
|
943
|
+
if (resolveInit) resolveInit(handle);
|
|
944
|
+
} catch {}
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Auto-initialize when loaded as a module
|
|
950
|
+
let handle = null;
|
|
951
|
+
|
|
952
|
+
// Create container dynamically with proper size
|
|
953
|
+
const container = document.createElement("div");
|
|
954
|
+
container.style.cssText = `
|
|
955
|
+
width: 100%;
|
|
956
|
+
height: 100vh;
|
|
957
|
+
position: absolute;
|
|
958
|
+
top: 0;
|
|
959
|
+
left: 0;
|
|
960
|
+
`;
|
|
961
|
+
document.body.appendChild(container);
|
|
962
|
+
|
|
963
|
+
function post(type, payload = {}) {
|
|
964
|
+
try {
|
|
965
|
+
parent.postMessage({ type, ...payload }, "*");
|
|
966
|
+
} catch {}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
async function bootstrap() {
|
|
970
|
+
try {
|
|
971
|
+
handle = await init({
|
|
972
|
+
container,
|
|
973
|
+
hdrPath: "/usd-viewer/environments/neutral.hdr",
|
|
974
|
+
});
|
|
975
|
+
} catch (e) {
|
|
976
|
+
console.warn("[USD Iframe] init error", e);
|
|
977
|
+
} finally {
|
|
978
|
+
post("IFRAME_READY");
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// helper to load entries [{ path, buffer(ArrayBuffer) }, ...]
|
|
983
|
+
async function loadFromEntries(entries, primaryPath) {
|
|
984
|
+
try {
|
|
985
|
+
if (!handle?.loadFromEntries) return;
|
|
986
|
+
await handle.loadFromEntries(entries, primaryPath);
|
|
987
|
+
} catch (e) {
|
|
988
|
+
console.warn("[USD Iframe] loadFromEntries error", e);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
window.addEventListener("message", async (evt) => {
|
|
993
|
+
const data = evt.data;
|
|
994
|
+
if (!data || typeof data !== "object") return;
|
|
995
|
+
try {
|
|
996
|
+
switch (data.type) {
|
|
997
|
+
case "USD_LOAD_URL":
|
|
998
|
+
post("USD_LOADING_START");
|
|
999
|
+
await handle?.loadFromURL?.(data.url);
|
|
1000
|
+
post("USD_LOADED");
|
|
1001
|
+
break;
|
|
1002
|
+
case "USD_CLEAR":
|
|
1003
|
+
await handle?.clear?.();
|
|
1004
|
+
break;
|
|
1005
|
+
case "USD_LOAD_ENTRIES":
|
|
1006
|
+
post("USD_LOADING_START");
|
|
1007
|
+
await loadFromEntries(data.entries || [], data.primaryPath);
|
|
1008
|
+
post("USD_LOADED");
|
|
1009
|
+
break;
|
|
1010
|
+
default:
|
|
1011
|
+
break;
|
|
1012
|
+
}
|
|
1013
|
+
} catch (e) {
|
|
1014
|
+
console.warn("[USD Iframe] message error", e);
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
bootstrap();
|