@vizij/render 0.0.1 → 0.0.3

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/dist/index.mjs CHANGED
@@ -393,6 +393,11 @@ function InnerRenderedGroup({
393
393
  setReference(group.id, namespace, ref);
394
394
  }
395
395
  }, [group.id, namespace, ref, setReference, refIsNull]);
396
+ useEffect2(() => {
397
+ if (ref.current) {
398
+ ref.current.name = group.name;
399
+ }
400
+ }, [group.name]);
396
401
  const handlePointerOver = useCallback(
397
402
  (event) => {
398
403
  if (event.eventObject !== event.object) {
@@ -557,6 +562,9 @@ function InnerRenderedEllipse({
557
562
  if (instanceOfRawRGB(color)) {
558
563
  materialRef.current.color.setRGB(color.r, color.g, color.b);
559
564
  materialRef.current.needsUpdate = true;
565
+ } else if (instanceOfRawVector32(color)) {
566
+ materialRef.current.color.setRGB(color.x, color.y, color.z);
567
+ materialRef.current.needsUpdate = true;
560
568
  } else if (instanceOfRawHSL(color)) {
561
569
  materialRef.current.color.setHSL(color.h, color.s, color.l);
562
570
  materialRef.current.needsUpdate = true;
@@ -609,6 +617,13 @@ function InnerRenderedEllipse({
609
617
  strokeColor.b
610
618
  );
611
619
  lineRef.current.material.needsUpdate = true;
620
+ } else if (instanceOfRawVector32(strokeColor)) {
621
+ lineRef.current.material.color.setRGB(
622
+ strokeColor.x,
623
+ strokeColor.y,
624
+ strokeColor.z
625
+ );
626
+ lineRef.current.material.needsUpdate = true;
612
627
  } else if (instanceOfRawHSL(strokeColor)) {
613
628
  lineRef.current.material.color.setHSL(
614
629
  strokeColor.h,
@@ -845,6 +860,9 @@ function InnerRenderedRectangle({
845
860
  if (instanceOfRawRGB2(color)) {
846
861
  materialRef.current.color.setRGB(color.r, color.g, color.b);
847
862
  materialRef.current.needsUpdate = true;
863
+ } else if (instanceOfRawVector33(color)) {
864
+ materialRef.current.color.setRGB(color.x, color.y, color.z);
865
+ materialRef.current.needsUpdate = true;
848
866
  } else if (instanceOfRawHSL2(color)) {
849
867
  materialRef.current.color.setHSL(color.h, color.s, color.l);
850
868
  materialRef.current.needsUpdate = true;
@@ -897,6 +915,13 @@ function InnerRenderedRectangle({
897
915
  strokeColor.b
898
916
  );
899
917
  lineRef.current.material.needsUpdate = true;
918
+ } else if (instanceOfRawVector33(strokeColor)) {
919
+ lineRef.current.material.color.setRGB(
920
+ strokeColor.x,
921
+ strokeColor.y,
922
+ strokeColor.z
923
+ );
924
+ lineRef.current.material.needsUpdate = true;
900
925
  } else if (instanceOfRawHSL2(strokeColor)) {
901
926
  lineRef.current.material.color.setHSL(
902
927
  strokeColor.h,
@@ -1052,7 +1077,17 @@ function InnerRenderedShape({
1052
1077
  });
1053
1078
  return av;
1054
1079
  }, [shape.features, animatables]);
1055
- const geometry = useMemo4(() => shape.geometry.clone(), [shape.geometry]);
1080
+ const materialName = useMemo4(
1081
+ () => deriveMaterialName(shape, animatableValues),
1082
+ [shape, animatableValues]
1083
+ );
1084
+ const geometry = useMemo4(() => {
1085
+ const cloned = shape.geometry.clone();
1086
+ if (shape.name) {
1087
+ cloned.name = shape.name;
1088
+ }
1089
+ return cloned;
1090
+ }, [shape.geometry, shape.name]);
1056
1091
  const selectionData = useMemo4(
1057
1092
  () => ({ id, namespace, type: "shape" }),
1058
1093
  [id, namespace]
@@ -1142,9 +1177,14 @@ function InnerRenderedShape({
1142
1177
  color.g,
1143
1178
  color.b
1144
1179
  );
1145
- if ((material.current || void 0)?.color) {
1146
- material.current.needsUpdate = true;
1147
- }
1180
+ material.current.needsUpdate = true;
1181
+ } else if ((material.current || void 0)?.color && instanceOfRawVector34(color)) {
1182
+ material.current.color.setRGB(
1183
+ color.x,
1184
+ color.y,
1185
+ color.z
1186
+ );
1187
+ material.current.needsUpdate = true;
1148
1188
  } else if (material.current && instanceOfRawHSL3(color)) {
1149
1189
  material.current.color.setHSL(
1150
1190
  color.h,
@@ -1167,6 +1207,21 @@ function InnerRenderedShape({
1167
1207
  useEffect5(() => {
1168
1208
  if (ref.current && refIsNull) setReference(shape.id, namespace, ref);
1169
1209
  }, [shape.id, namespace, ref, setReference, refIsNull]);
1210
+ useEffect5(() => {
1211
+ if (ref.current) {
1212
+ ref.current.name = shape.name;
1213
+ }
1214
+ }, [shape.name]);
1215
+ useEffect5(() => {
1216
+ if (!material.current) {
1217
+ return;
1218
+ }
1219
+ if (materialName) {
1220
+ material.current.name = materialName;
1221
+ } else if (shape.name) {
1222
+ material.current.name = shape.name;
1223
+ }
1224
+ }, [materialName, shape.name]);
1170
1225
  const handlePointerOver = useCallback4(
1171
1226
  (event) => {
1172
1227
  event.stopPropagation();
@@ -1253,6 +1308,51 @@ function InnerRenderedShape({
1253
1308
  );
1254
1309
  }
1255
1310
  var RenderedShape = memo5(InnerRenderedShape);
1311
+ var MATERIAL_FEATURE_KEYS = [
1312
+ "color",
1313
+ "opacity",
1314
+ "roughness",
1315
+ "metalness",
1316
+ "shininess"
1317
+ ];
1318
+ var MATERIAL_NAME_SUFFIXES = [
1319
+ " color",
1320
+ " colours",
1321
+ " colour",
1322
+ " opacity",
1323
+ " roughness",
1324
+ " metalness",
1325
+ " shininess"
1326
+ ];
1327
+ function deriveMaterialName(shape, values) {
1328
+ for (const key of MATERIAL_FEATURE_KEYS) {
1329
+ const feature = shape.features[key];
1330
+ if (feature && feature.animated) {
1331
+ const animatable = values[feature.value];
1332
+ const candidate = extractMaterialName(animatable?.name);
1333
+ if (candidate) {
1334
+ return candidate;
1335
+ }
1336
+ }
1337
+ }
1338
+ return void 0;
1339
+ }
1340
+ function extractMaterialName(name) {
1341
+ if (!name) {
1342
+ return void 0;
1343
+ }
1344
+ const trimmed = name.trim();
1345
+ if (!trimmed) {
1346
+ return void 0;
1347
+ }
1348
+ const lowered = trimmed.toLowerCase();
1349
+ for (const suffix of MATERIAL_NAME_SUFFIXES) {
1350
+ if (lowered.endsWith(suffix)) {
1351
+ return trimmed.slice(0, trimmed.length - suffix.length).trim();
1352
+ }
1353
+ }
1354
+ return trimmed;
1355
+ }
1256
1356
 
1257
1357
  // src/renderables/renderable.tsx
1258
1358
  import { Fragment as Fragment3, jsx as jsx6 } from "react/jsx-runtime";
@@ -2426,9 +2526,12 @@ function traverseThree(group, namespaces, aggressiveImport = false, rootBounds)
2426
2526
  });
2427
2527
  const useRobotData = !aggressiveImport || hasRobotData;
2428
2528
  if (useRobotData) {
2429
- group.traverse((child) => {
2529
+ const stack = [group];
2530
+ while (stack.length > 0) {
2531
+ const child = stack.pop();
2430
2532
  if (child.userData?.gltfExtensions?.RobotData) {
2431
2533
  const data = child.userData.gltfExtensions.RobotData;
2534
+ applyStoredRenderableNames(child, data);
2432
2535
  let loadedData;
2433
2536
  let mappedFeatures;
2434
2537
  let animatableValues;
@@ -2494,7 +2597,10 @@ function traverseThree(group, namespaces, aggressiveImport = false, rootBounds)
2494
2597
  throw new Error(`Unhandled type`);
2495
2598
  }
2496
2599
  }
2497
- });
2600
+ if (child.children) {
2601
+ stack.push(...child.children);
2602
+ }
2603
+ }
2498
2604
  } else {
2499
2605
  const derivedRootBounds = rootBounds ?? deriveRootBounds(group);
2500
2606
  if (!derivedRootBounds) {
@@ -2510,6 +2616,94 @@ function traverseThree(group, namespaces, aggressiveImport = false, rootBounds)
2510
2616
  }
2511
2617
  return [worldData, animatableData];
2512
2618
  }
2619
+ var MATERIAL_NAME_FEATURE_KEYS = [
2620
+ "color",
2621
+ "opacity",
2622
+ "roughness",
2623
+ "metalness",
2624
+ "shininess"
2625
+ ];
2626
+ var MATERIAL_NAME_SUFFIXES2 = [
2627
+ " color",
2628
+ " colours",
2629
+ " colour",
2630
+ " opacity",
2631
+ " roughness",
2632
+ " metalness",
2633
+ " shininess"
2634
+ ];
2635
+ function applyStoredRenderableNames(object, data) {
2636
+ if (typeof data.name === "string" && data.name.length > 0) {
2637
+ object.name = data.name;
2638
+ }
2639
+ if (object.isGroup) {
2640
+ const group = object;
2641
+ if (typeof data.name === "string" && data.name.length > 0) {
2642
+ group.name = data.name;
2643
+ }
2644
+ }
2645
+ if (object.isMesh) {
2646
+ const mesh = object;
2647
+ if (typeof data.name === "string" && data.name.length > 0) {
2648
+ mesh.name = data.name;
2649
+ if (mesh.geometry) {
2650
+ mesh.geometry.name = data.name;
2651
+ }
2652
+ }
2653
+ const inferredName = inferMaterialNameFromStoredRenderable(data);
2654
+ if (inferredName) {
2655
+ assignMaterialName(mesh.material, inferredName);
2656
+ }
2657
+ }
2658
+ }
2659
+ function assignMaterialName(material, name) {
2660
+ if (!material) {
2661
+ return;
2662
+ }
2663
+ if (Array.isArray(material)) {
2664
+ material.forEach((mat) => {
2665
+ mat.name = name;
2666
+ });
2667
+ } else {
2668
+ material.name = name;
2669
+ }
2670
+ }
2671
+ function inferMaterialNameFromStoredRenderable(data) {
2672
+ if (data.type !== "shape") {
2673
+ return void 0;
2674
+ }
2675
+ const features = data.features;
2676
+ for (const key of MATERIAL_NAME_FEATURE_KEYS) {
2677
+ const candidate = extractMaterialNameFromFeature(features[key]);
2678
+ if (candidate) {
2679
+ return candidate;
2680
+ }
2681
+ }
2682
+ return void 0;
2683
+ }
2684
+ function extractMaterialNameFromFeature(feature) {
2685
+ if (!isStoredAnimatedFeature(feature)) {
2686
+ return void 0;
2687
+ }
2688
+ const animatableName = feature.value.name;
2689
+ if (!animatableName) {
2690
+ return void 0;
2691
+ }
2692
+ return stripMaterialSuffixes(animatableName);
2693
+ }
2694
+ function stripMaterialSuffixes(name) {
2695
+ const trimmed = name.trim();
2696
+ const lowered = trimmed.toLowerCase();
2697
+ for (const suffix of MATERIAL_NAME_SUFFIXES2) {
2698
+ if (lowered.endsWith(suffix)) {
2699
+ return trimmed.slice(0, trimmed.length - suffix.length).trim();
2700
+ }
2701
+ }
2702
+ return trimmed;
2703
+ }
2704
+ function isStoredAnimatedFeature(feature) {
2705
+ return Boolean(feature) && typeof feature === "object" && feature.animated === true && "value" in feature;
2706
+ }
2513
2707
  function isGroupFeatures(value) {
2514
2708
  if (!value || typeof value !== "object") {
2515
2709
  throw new Error("Expected object");
@@ -2574,6 +2768,106 @@ function deriveRootBounds(group) {
2574
2768
  };
2575
2769
  }
2576
2770
 
2771
+ // src/functions/vizij-bundle.ts
2772
+ var BUNDLE_KEYS = ["VIZIJ_bundle"];
2773
+ function cloneBundle(value) {
2774
+ return JSON.parse(JSON.stringify(value));
2775
+ }
2776
+ function readExtensionValue(extensionContainer) {
2777
+ for (const key of BUNDLE_KEYS) {
2778
+ if (extensionContainer && Object.prototype.hasOwnProperty.call(extensionContainer, key)) {
2779
+ const value = extensionContainer[key];
2780
+ if (value && typeof value === "object") {
2781
+ return { key, value };
2782
+ }
2783
+ }
2784
+ }
2785
+ return null;
2786
+ }
2787
+ function searchObjectForBundle(object) {
2788
+ const stack = [object];
2789
+ while (stack.length > 0) {
2790
+ const current = stack.pop();
2791
+ const extensions = current?.userData?.gltfExtensions ?? current?.userData?.extensions ?? null;
2792
+ if (extensions && typeof extensions === "object") {
2793
+ const match = readExtensionValue(extensions);
2794
+ if (match) {
2795
+ return cloneBundle(match.value);
2796
+ }
2797
+ }
2798
+ if (current.children && current.children.length > 0) {
2799
+ stack.push(...current.children);
2800
+ }
2801
+ }
2802
+ return null;
2803
+ }
2804
+ function searchParserJsonForBundle(parserJson) {
2805
+ if (!parserJson || typeof parserJson !== "object") {
2806
+ return null;
2807
+ }
2808
+ const nodes = Array.isArray(parserJson.nodes) ? parserJson.nodes : [];
2809
+ for (const node of nodes) {
2810
+ const extensions = node && typeof node === "object" ? node.extensions : null;
2811
+ if (extensions && typeof extensions === "object") {
2812
+ const match = readExtensionValue(extensions);
2813
+ if (match) {
2814
+ return cloneBundle(match.value);
2815
+ }
2816
+ }
2817
+ }
2818
+ const scenes = Array.isArray(parserJson.scenes) ? parserJson.scenes : [];
2819
+ for (const scene of scenes) {
2820
+ const extensions = scene && typeof scene === "object" ? scene.extensions : null;
2821
+ if (extensions && typeof extensions === "object") {
2822
+ const match = readExtensionValue(extensions);
2823
+ if (match) {
2824
+ return cloneBundle(match.value);
2825
+ }
2826
+ }
2827
+ }
2828
+ return null;
2829
+ }
2830
+ function extractVizijBundle(object, parserJson) {
2831
+ const fromObject = searchObjectForBundle(object);
2832
+ if (fromObject) {
2833
+ return fromObject;
2834
+ }
2835
+ const fromParser = searchParserJsonForBundle(parserJson);
2836
+ if (fromParser) {
2837
+ return fromParser;
2838
+ }
2839
+ return null;
2840
+ }
2841
+ function applyVizijBundle(object, bundle) {
2842
+ const userData = object.userData && typeof object.userData === "object" ? object.userData : {};
2843
+ const originalExtensions = userData.gltfExtensions;
2844
+ let applied = false;
2845
+ if (bundle) {
2846
+ userData.gltfExtensions = {
2847
+ ...originalExtensions ?? {},
2848
+ VIZIJ_bundle: bundle
2849
+ };
2850
+ object.userData = userData;
2851
+ applied = true;
2852
+ }
2853
+ return () => {
2854
+ if (!applied) {
2855
+ return;
2856
+ }
2857
+ if (originalExtensions) {
2858
+ userData.gltfExtensions = originalExtensions;
2859
+ } else {
2860
+ if (userData.gltfExtensions) {
2861
+ delete userData.gltfExtensions;
2862
+ }
2863
+ if (Object.keys(userData).length === 0) {
2864
+ delete object.userData;
2865
+ }
2866
+ }
2867
+ applied = false;
2868
+ };
2869
+ }
2870
+
2577
2871
  // src/functions/load-gltf.ts
2578
2872
  THREE5.Object3D.DEFAULT_UP.set(0, 0, 1);
2579
2873
  var EmptyModelError = class extends Error {
@@ -2587,24 +2881,27 @@ async function loadGLTF(url, namespaces, aggressiveImport = false, rootBounds) {
2587
2881
  modelLoader.setDRACOLoader(new DRACOLoader());
2588
2882
  const modelData = await modelLoader.loadAsync(url);
2589
2883
  const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
2590
- return traverseThree(
2884
+ const asset = parseScene(
2591
2885
  modelData.scene,
2592
2886
  actualizedNamespaces,
2593
2887
  aggressiveImport,
2594
- rootBounds
2888
+ rootBounds,
2889
+ modelData?.parser?.json
2595
2890
  );
2891
+ return [asset.world, asset.animatables];
2596
2892
  }
2597
2893
  async function loadGLTFFromBlob(blob, namespaces, aggressiveImport = false, rootBounds) {
2598
2894
  const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
2599
2895
  if (typeof URL !== "undefined" && typeof URL.createObjectURL === "function") {
2600
2896
  const objectUrl = URL.createObjectURL(blob);
2601
2897
  try {
2602
- return await loadGLTF(
2898
+ const asset = await loadGLTFWithBundle(
2603
2899
  objectUrl,
2604
2900
  actualizedNamespaces,
2605
2901
  aggressiveImport,
2606
2902
  rootBounds
2607
2903
  );
2904
+ return [asset.world, asset.animatables];
2608
2905
  } finally {
2609
2906
  URL.revokeObjectURL(objectUrl);
2610
2907
  }
@@ -2618,14 +2915,83 @@ async function loadGLTFFromBlob(blob, namespaces, aggressiveImport = false, root
2618
2915
  "",
2619
2916
  (gltf) => {
2620
2917
  try {
2621
- resolve(
2622
- traverseThree(
2623
- gltf.scene,
2624
- actualizedNamespaces,
2625
- aggressiveImport,
2626
- rootBounds
2627
- )
2918
+ const asset = parseScene(
2919
+ gltf.scene,
2920
+ actualizedNamespaces,
2921
+ aggressiveImport,
2922
+ rootBounds,
2923
+ gltf?.parser?.json
2628
2924
  );
2925
+ resolve([asset.world, asset.animatables]);
2926
+ } catch (error) {
2927
+ if (error instanceof Error) {
2928
+ reject(error);
2929
+ } else {
2930
+ reject(new Error(String(error)));
2931
+ }
2932
+ }
2933
+ },
2934
+ (error) => {
2935
+ reject(new Error(`Error loading GLTF: ${error.message}`));
2936
+ }
2937
+ );
2938
+ });
2939
+ }
2940
+ function parseScene(scene, namespaces, aggressiveImport, rootBounds, parserJson) {
2941
+ const [world, animatables] = traverseThree(
2942
+ scene,
2943
+ namespaces,
2944
+ aggressiveImport,
2945
+ rootBounds
2946
+ );
2947
+ const bundle = extractVizijBundle(scene, parserJson);
2948
+ return { world, animatables, bundle };
2949
+ }
2950
+ async function loadGLTFWithBundle(url, namespaces, aggressiveImport = false, rootBounds) {
2951
+ const modelLoader = new GLTFLoader();
2952
+ modelLoader.setDRACOLoader(new DRACOLoader());
2953
+ const modelData = await modelLoader.loadAsync(url);
2954
+ const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
2955
+ return parseScene(
2956
+ modelData.scene,
2957
+ actualizedNamespaces,
2958
+ aggressiveImport,
2959
+ rootBounds,
2960
+ modelData?.parser?.json
2961
+ );
2962
+ }
2963
+ async function loadGLTFFromBlobWithBundle(blob, namespaces, aggressiveImport = false, rootBounds) {
2964
+ const actualizedNamespaces = namespaces.length > 0 ? namespaces : ["default"];
2965
+ if (typeof URL !== "undefined" && typeof URL.createObjectURL === "function") {
2966
+ const objectUrl = URL.createObjectURL(blob);
2967
+ try {
2968
+ return await loadGLTFWithBundle(
2969
+ objectUrl,
2970
+ actualizedNamespaces,
2971
+ aggressiveImport,
2972
+ rootBounds
2973
+ );
2974
+ } finally {
2975
+ URL.revokeObjectURL(objectUrl);
2976
+ }
2977
+ }
2978
+ const arrayBuffer = typeof blob.arrayBuffer === "function" ? await blob.arrayBuffer() : await new Response(blob).arrayBuffer();
2979
+ return new Promise((resolve, reject) => {
2980
+ const loader = new GLTFLoader();
2981
+ loader.setDRACOLoader(new DRACOLoader());
2982
+ loader.parse(
2983
+ arrayBuffer,
2984
+ "",
2985
+ (gltf) => {
2986
+ try {
2987
+ const asset = parseScene(
2988
+ gltf.scene,
2989
+ actualizedNamespaces,
2990
+ aggressiveImport,
2991
+ rootBounds,
2992
+ gltf?.parser?.json
2993
+ );
2994
+ resolve(asset);
2629
2995
  } catch (error) {
2630
2996
  if (error instanceof Error) {
2631
2997
  reject(error);
@@ -2674,36 +3040,62 @@ var loadGltfFromBlob = (blob, namespaces) => {
2674
3040
  import { GLTFExporter } from "three-stdlib";
2675
3041
  import * as THREE6 from "three";
2676
3042
  THREE6.Object3D.DEFAULT_UP.set(0, 0, 1);
2677
- function exportScene(data, fileName = "scene.glb") {
3043
+ function exportScene(data, fileNameOrOptions = "scene.glb") {
3044
+ const options = typeof fileNameOrOptions === "string" ? { fileName: fileNameOrOptions } : fileNameOrOptions ?? {};
3045
+ const fileName = options.fileName ?? "scene.glb";
3046
+ const animationClips = Array.isArray(options.animations) ? options.animations.filter(Boolean) : [];
3047
+ const shouldAttachBundle = Boolean(options.bundle);
2678
3048
  const exporter = new GLTFExporter();
2679
- exporter.parse(
2680
- data,
2681
- (gltf) => {
2682
- if (!(gltf instanceof ArrayBuffer)) {
2683
- throw new Error("Failed to export scene!");
2684
- }
2685
- const link = document.createElement("a");
2686
- link.href = URL.createObjectURL(
2687
- new Blob([gltf], {
2688
- type: "application/octet-stream"
2689
- })
2690
- );
2691
- const trimmed = fileName.trim();
2692
- const safeFileName = trimmed.length > 0 ? trimmed : "scene.glb";
2693
- const downloadName = safeFileName.toLowerCase().endsWith(".glb") ? safeFileName : `${safeFileName}.glb`;
2694
- link.download = downloadName;
2695
- link.click();
2696
- URL.revokeObjectURL(link.href);
2697
- },
2698
- () => {
2699
- },
2700
- {
2701
- trs: true,
2702
- onlyVisible: false,
2703
- binary: true,
2704
- includeCustomExtensions: true
3049
+ exporter.register(() => ({
3050
+ writeMesh(mesh, meshDef) {
3051
+ const meshName = mesh.name?.trim() || mesh.geometry?.name?.trim() || void 0;
3052
+ if (meshName) {
3053
+ meshDef.name = meshName;
3054
+ }
2705
3055
  }
2706
- );
3056
+ }));
3057
+ const detachBundle = shouldAttachBundle && options.bundle ? applyVizijBundle(data, options.bundle) : () => {
3058
+ };
3059
+ const binary = options.binary ?? true;
3060
+ const exporterOptions = {
3061
+ trs: true,
3062
+ onlyVisible: false,
3063
+ binary,
3064
+ includeCustomExtensions: true
3065
+ };
3066
+ if (animationClips.length > 0) {
3067
+ exporterOptions.animations = animationClips;
3068
+ }
3069
+ try {
3070
+ exporter.parse(
3071
+ data,
3072
+ (gltf) => {
3073
+ detachBundle();
3074
+ if (!(gltf instanceof ArrayBuffer)) {
3075
+ throw new Error("Failed to export scene!");
3076
+ }
3077
+ const link = document.createElement("a");
3078
+ link.href = URL.createObjectURL(
3079
+ new Blob([gltf], {
3080
+ type: "application/octet-stream"
3081
+ })
3082
+ );
3083
+ const trimmed = fileName.trim();
3084
+ const safeFileName = trimmed.length > 0 ? trimmed : "scene.glb";
3085
+ const downloadName = safeFileName.toLowerCase().endsWith(".glb") ? safeFileName : `${safeFileName}.glb`;
3086
+ link.download = downloadName;
3087
+ link.click();
3088
+ URL.revokeObjectURL(link.href);
3089
+ },
3090
+ () => {
3091
+ detachBundle();
3092
+ },
3093
+ exporterOptions
3094
+ );
3095
+ } catch (error) {
3096
+ detachBundle();
3097
+ throw error;
3098
+ }
2707
3099
  }
2708
3100
  export {
2709
3101
  Controller,
@@ -2713,10 +3105,14 @@ export {
2713
3105
  Vizij,
2714
3106
  VizijContext,
2715
3107
  VizijSlice,
3108
+ applyVizijBundle,
2716
3109
  createVizijStore,
2717
3110
  exportScene,
3111
+ extractVizijBundle,
2718
3112
  loadGLTF,
2719
3113
  loadGLTFFromBlob,
3114
+ loadGLTFFromBlobWithBundle,
3115
+ loadGLTFWithBundle,
2720
3116
  loadGltfFromBlob,
2721
3117
  useDefaultVizijStore,
2722
3118
  useFeatures,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vizij/render",
3
3
  "description": "Higher-level visualization and interaction components for robot and ai faces.",
4
- "version": "0.0.1",
4
+ "version": "0.0.3",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
@@ -31,7 +31,7 @@
31
31
  "lint:fix": "pnpm --filter \"$npm_package_name\" exec eslint --ext .js,.jsx,.ts,.tsx --fix -- .",
32
32
  "prettier:check": "prettier --check .",
33
33
  "prettier:write": "prettier --write .",
34
- "test": "vitest --run --passWithNoTests",
34
+ "test": "node --loader ./tests/node-ts-loader.mjs --test tests/*.node-test.mjs",
35
35
  "clean": "rm -rf dist .turbo coverage tsconfig.tsbuildinfo",
36
36
  "reset": "rm -rf node_modules",
37
37
  "reset:hard": "pnpm run reset && rm -f pnpm-lock.yaml package-lock.json yarn.lock",
@@ -72,8 +72,7 @@
72
72
  "zustand": "^5.0.2",
73
73
  "tsup": "^8.0.1",
74
74
  "typescript": "^5.5.0",
75
- "prettier": "^3.4.2",
76
- "vitest": "^3.2.4"
75
+ "prettier": "^3.4.2"
77
76
  },
78
77
  "size-limit": [
79
78
  {